diff --git a/packages/synapse-sdk/src/payments/service.ts b/packages/synapse-sdk/src/payments/service.ts index 12f82545..b505bd94 100644 --- a/packages/synapse-sdk/src/payments/service.ts +++ b/packages/synapse-sdk/src/payments/service.ts @@ -40,6 +40,7 @@ export class PaymentsService { private readonly _paymentsAddress: string private readonly _usdfcAddress: string private readonly _disableNonceManager: boolean + private readonly _multicall3Address: string | null // Cached contract instances private _usdfcContract: ethers.Contract | null = null private _paymentsContract: ethers.Contract | null = null @@ -60,13 +61,15 @@ export class PaymentsService { signer: ethers.Signer, paymentsAddress: string, usdfcAddress: string, - disableNonceManager: boolean + disableNonceManager: boolean, + multicall3Address: string | null = null ) { this._provider = provider this._signer = signer this._paymentsAddress = paymentsAddress this._usdfcAddress = usdfcAddress this._disableNonceManager = disableNonceManager + this._multicall3Address = multicall3Address } /** @@ -108,7 +111,15 @@ export class PaymentsService { const chainId = CHAIN_IDS[networkType] // Setup Multicall3 for batched RPC calls - const multicall3Address = CONTRACT_ADDRESSES.MULTICALL3[networkType] + const multicall3Address = + this._multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[networkType as keyof typeof CONTRACT_ADDRESSES.MULTICALL3] + if (!multicall3Address) { + throw createError( + 'PaymentsService', + '_getPermitSignature', + `No Multicall3 address available for network: ${networkType}` + ) + } const multicall = new ethers.Contract(multicall3Address, CONTRACT_ABIS.MULTICALL3, this._provider) // Create interfaces for encoding/decoding diff --git a/packages/synapse-sdk/src/session/key.ts b/packages/synapse-sdk/src/session/key.ts index 22efcb27..f625b513 100644 --- a/packages/synapse-sdk/src/session/key.ts +++ b/packages/synapse-sdk/src/session/key.ts @@ -53,17 +53,20 @@ export class SessionKey { private readonly _registry: ethers.Contract private readonly _signer: ethers.Signer private readonly _owner: ethers.Signer + private readonly _multicall3Address: string | null public constructor( provider: ethers.Provider, sessionKeyRegistryAddress: string, signer: ethers.Signer, - owner: ethers.Signer + owner: ethers.Signer, + multicall3Address: string | null = null ) { this._provider = provider this._registry = new ethers.Contract(sessionKeyRegistryAddress, CONTRACT_ABIS.SESSION_KEY_REGISTRY, owner) this._signer = signer this._owner = owner + this._multicall3Address = multicall3Address } getSigner(): ethers.Signer { @@ -78,11 +81,13 @@ export class SessionKey { async fetchExpiries(permissions: string[] = PDP_PERMISSIONS): Promise> { const network = await getFilecoinNetworkType(this._provider) - const multicall = new ethers.Contract( - CONTRACT_ADDRESSES.MULTICALL3[network], - CONTRACT_ABIS.MULTICALL3, - this._provider - ) + const multicall3Address = + this._multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[network as keyof typeof CONTRACT_ADDRESSES.MULTICALL3] + if (!multicall3Address) { + throw new Error(`No Multicall3 address available for network: ${network}`) + } + + const multicall = new ethers.Contract(multicall3Address, CONTRACT_ABIS.MULTICALL3, this._provider) const registryInterface = new ethers.Interface(CONTRACT_ABIS.SESSION_KEY_REGISTRY) const [ownerAddress, signerAddress, registryAddress] = await Promise.all([ diff --git a/packages/synapse-sdk/src/sp-registry/service.ts b/packages/synapse-sdk/src/sp-registry/service.ts index d1481e36..5f2219ec 100644 --- a/packages/synapse-sdk/src/sp-registry/service.ts +++ b/packages/synapse-sdk/src/sp-registry/service.ts @@ -38,14 +38,16 @@ import type { export class SPRegistryService { private readonly _provider: ethers.Provider private readonly _registryAddress: string + private readonly _multicall3Address: string | null private _registryContract: ethers.Contract | null = null /** * Constructor for SPRegistryService */ - constructor(provider: ethers.Provider, registryAddress: string) { + constructor(provider: ethers.Provider, registryAddress: string, multicall3Address: string | null = null) { this._provider = provider this._registryAddress = registryAddress + this._multicall3Address = multicall3Address } /** @@ -453,7 +455,11 @@ export class SPRegistryService { */ private async _getProvidersWithMulticall(providerIds: number[]): Promise { const network = await getFilecoinNetworkType(this._provider) - const multicall3Address = CONTRACT_ADDRESSES.MULTICALL3[network] + const multicall3Address = + this._multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[network as keyof typeof CONTRACT_ADDRESSES.MULTICALL3] + if (!multicall3Address) { + throw new Error(`No Multicall3 address available for network: ${network}`) + } const multicall = new ethers.Contract(multicall3Address, CONTRACT_ABIS.MULTICALL3, this._provider) const iface = new ethers.Interface(CONTRACT_ABIS.SERVICE_PROVIDER_REGISTRY) diff --git a/packages/synapse-sdk/src/synapse.ts b/packages/synapse-sdk/src/synapse.ts index cbf6099c..d4db9130 100644 --- a/packages/synapse-sdk/src/synapse.ts +++ b/packages/synapse-sdk/src/synapse.ts @@ -22,7 +22,13 @@ import type { SubgraphConfig, SynapseOptions, } from './types.ts' -import { CHAIN_IDS, CONTRACT_ADDRESSES, getFilecoinNetworkType } from './utils/index.ts' +import { + CHAIN_IDS, + CONTRACT_ADDRESSES, + GENESIS_TIMESTAMPS, + getFilecoinNetworkType, + queryGenesisTimestamp, +} from './utils/index.ts' import { WarmStorageService } from './warm-storage/index.ts' export class Synapse { @@ -36,6 +42,8 @@ export class Synapse { private readonly _pieceRetriever: PieceRetriever private readonly _storageManager: StorageManager private _session: SessionKey | null = null + private readonly _genesisTimestamp: number + private readonly _multicall3Address: string /** * Create a new Synapse instance with async initialization. @@ -118,31 +126,59 @@ export class Synapse { } // Final network validation - if (network !== 'mainnet' && network !== 'calibration') { - throw new Error(`Invalid network: ${String(network)}. Only 'mainnet' and 'calibration' are supported.`) + if (network !== 'mainnet' && network !== 'calibration' && network !== 'devnet') { + throw new Error(`Invalid network: ${String(network)}. Only 'mainnet', 'calibration', and 'devnet' are supported.`) + } + + const genesisTimestamp = + options.genesisTimestamp ?? + (network === 'devnet' ? await queryGenesisTimestamp(provider) : GENESIS_TIMESTAMPS[network]) + + const resolvedMulticall3Address = + options.multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[network as keyof typeof CONTRACT_ADDRESSES.MULTICALL3] + if (!resolvedMulticall3Address) { + throw new Error( + network === 'devnet' + ? 'multicall3Address is required when using devnet' + : `No Multicall3 address configured for network: ${network}` + ) } // Create Warm Storage service with initialized addresses - const warmStorageAddress = options.warmStorageAddress ?? CONTRACT_ADDRESSES.WARM_STORAGE[network] - if (!warmStorageAddress) { - throw new Error(`No Warm Storage address configured for network: ${network}`) + const resolvedWarmStorageAddress = + options.warmStorageAddress ?? CONTRACT_ADDRESSES.WARM_STORAGE[network as keyof typeof CONTRACT_ADDRESSES.WARM_STORAGE] + if (!resolvedWarmStorageAddress) { + throw new Error( + network === 'devnet' + ? 'warmStorageAddress is required when using devnet' + : `No Warm Storage address configured for network: ${network}` + ) } - const warmStorageService = await WarmStorageService.create(provider, warmStorageAddress) + const warmStorageService = await WarmStorageService.create( + provider, + resolvedWarmStorageAddress, + resolvedMulticall3Address, + options.warmStorageViewAddress ?? null + ) + + const withCDNEnabled = network !== 'devnet' && options.withCDN === true + const withIpniEnabled = network === 'devnet' ? false : options.withIpni // Create payments service with discovered addresses const paymentsAddress = warmStorageService.getPaymentsAddress() - const usdfcAddress = warmStorageService.getUSDFCTokenAddress() + const usdfcAddress = options.usdfcAddress ?? warmStorageService.getUSDFCTokenAddress() const payments = new PaymentsService( provider, signer, paymentsAddress, usdfcAddress, - options.disableNonceManager === true + options.disableNonceManager === true, + resolvedMulticall3Address ) // Create SPRegistryService for use in retrievers const registryAddress = warmStorageService.getServiceProviderRegistryAddress() - const spRegistry = new SPRegistryService(provider, registryAddress) + const spRegistry = new SPRegistryService(provider, registryAddress, resolvedMulticall3Address) // Initialize piece retriever (use provided or create default) let pieceRetriever: PieceRetriever @@ -154,7 +190,7 @@ export class Synapse { // Check for subgraph option let baseRetriever: PieceRetriever = chainRetriever - if (options.subgraphConfig != null || options.subgraphService != null) { + if (withIpniEnabled !== false && (options.subgraphConfig != null || options.subgraphService != null)) { const subgraphService = options.subgraphService != null ? options.subgraphService @@ -175,12 +211,14 @@ export class Synapse { provider, network, payments, - options.withCDN === true, - warmStorageAddress, + withCDNEnabled, + resolvedWarmStorageAddress, warmStorageService, pieceRetriever, options.dev === false, - options.withIpni + withIpniEnabled, + genesisTimestamp, + resolvedMulticall3Address ) } @@ -190,12 +228,13 @@ export class Synapse { network: FilecoinNetworkType, payments: PaymentsService, withCDN: boolean, - warmStorageAddress: string, warmStorageService: WarmStorageService, pieceRetriever: PieceRetriever, dev: boolean, - withIpni?: boolean + withIpni: boolean | undefined, + genesisTimestamp: number, + multicall3Address: string ) { this._signer = signer this._provider = provider @@ -206,6 +245,8 @@ export class Synapse { this._pieceRetriever = pieceRetriever this._warmStorageAddress = warmStorageAddress this._session = null + this._genesisTimestamp = genesisTimestamp + this._multicall3Address = multicall3Address // Initialize StorageManager this._storageManager = new StorageManager( @@ -273,7 +314,8 @@ export class Synapse { this._provider, this._warmStorageService.getSessionKeyRegistryAddress(), sessionKeySigner, - this._signer + this._signer, + this._multicall3Address ) } @@ -314,7 +356,27 @@ export class Synapse { * @returns The numeric chain ID */ getChainId(): number { - return this._network === 'mainnet' ? CHAIN_IDS.mainnet : CHAIN_IDS.calibration + return this._network === 'mainnet' + ? CHAIN_IDS.mainnet + : this._network === 'calibration' + ? CHAIN_IDS.calibration + : CHAIN_IDS.devnet + } + + /** + * Gets the genesis timestamp for the current network + * @returns Genesis timestamp in seconds (Unix timestamp) + */ + getGenesisTimestamp(): number { + return this._genesisTimestamp + } + + /** + * Gets the Multicall3 contract address in use + * @returns The Multicall3 address as a string + */ + getMulticall3Address(): string { + return this._multicall3Address } /** @@ -341,6 +403,46 @@ export class Synapse { return this._warmStorageService.getPDPVerifierAddress() } + /** + * Gets the USDFC token address for the current network + * @returns The USDFC token address + */ + getUSDFCTokenAddress(): string { + return this._warmStorageService.getUSDFCTokenAddress() + } + + /** + * Gets the Service Provider Registry address for the current network + * @returns The Service Provider Registry address + */ + getServiceProviderRegistryAddress(): string { + return this._warmStorageService.getServiceProviderRegistryAddress() + } + + /** + * Gets the Session Key Registry address for the current network + * @returns The Session Key Registry address + */ + getSessionKeyRegistryAddress(): string { + return this._warmStorageService.getSessionKeyRegistryAddress() + } + + /** + * Gets the Warm Storage View contract address for the current network + * @returns The Warm Storage View contract address + */ + getWarmStorageViewAddress(): string { + return this._warmStorageService.getViewContractAddress() + } + + /** + * Gets the FilBeam Beneficiary address for the current network + * @returns The FilBeam Beneficiary address + */ + getFilBeamBeneficiaryAddress(): string { + return this._warmStorageService.getFilBeamBeneficiaryAddress() + } + /** * Gets the payment service instance * @returns The payment service @@ -436,7 +538,7 @@ export class Synapse { // Create SPRegistryService const registryAddress = this._warmStorageService.getServiceProviderRegistryAddress() - const spRegistry = new SPRegistryService(this._provider, registryAddress) + const spRegistry = new SPRegistryService(this._provider, registryAddress, this._multicall3Address) let providerInfo: ProviderInfo | null if (typeof providerAddress === 'string') { diff --git a/packages/synapse-sdk/src/test/mocks/jsonrpc/index.ts b/packages/synapse-sdk/src/test/mocks/jsonrpc/index.ts index 7a2150e6..feced5c8 100644 --- a/packages/synapse-sdk/src/test/mocks/jsonrpc/index.ts +++ b/packages/synapse-sdk/src/test/mocks/jsonrpc/index.ts @@ -164,7 +164,10 @@ function handler(body: RpcRequest, options: JSONRPCOptions) { return warmStorageCallHandler(data as Hex, options) } - if (isAddressEqual(CONTRACT_ADDRESSES.MULTICALL3.calibration, to as Address)) { + if ( + isAddressEqual(CONTRACT_ADDRESSES.MULTICALL3.calibration, to as Address) || + isAddressEqual('0x1000000000000000000000000000000000000001' as Address, to as Address) + ) { return multicall3CallHandler(data as Hex, options) } diff --git a/packages/synapse-sdk/src/test/synapse.test.ts b/packages/synapse-sdk/src/test/synapse.test.ts index 4e801125..ce6d6cdb 100644 --- a/packages/synapse-sdk/src/test/synapse.test.ts +++ b/packages/synapse-sdk/src/test/synapse.test.ts @@ -170,6 +170,175 @@ describe('Synapse', () => { }) }) + describe('Devnet Support', () => { + it('should create instance with devnet chain ID (31415926)', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', // 31415926 in hex + }) + ) + const devnetAddresses = { + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + usdfcAddress: '0x2000000000000000000000000000000000000001', + } + const synapse = await Synapse.create({ + provider, + ...devnetAddresses, + }) + assert.exists(synapse) + assert.exists(synapse.payments) + assert.isTrue(synapse.payments instanceof PaymentsService) + }) + + it('should require multicall3Address for devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', // devnet chain ID + }) + ) + try { + await Synapse.create({ + provider, + warmStorageAddress: ADDRESSES.calibration.warmStorage, + usdfcAddress: '0x2000000000000000000000000000000000000001', + // Missing multicall3Address + }) + assert.fail('Should have thrown for missing multicall3Address') + } catch (error: any) { + assert.include(error.message, 'multicall3Address') + assert.include(error.message, 'devnet') + } + }) + + it('should require warmStorageAddress for devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + try { + await Synapse.create({ + provider, + multicall3Address: '0x1000000000000000000000000000000000000001', + usdfcAddress: '0x2000000000000000000000000000000000000001', + // Missing warmStorageAddress + }) + assert.fail('Should have thrown for missing warmStorageAddress') + } catch (error: any) { + assert.include(error.message, 'warmStorageAddress') + assert.include(error.message, 'devnet') + } + }) + + it('should allow usdfcAddress to be auto-discovered from warmStorage on devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + // usdfcAddress is optional - it will be auto-discovered from warmStorage contract + const synapse = await Synapse.create({ + provider, + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + // usdfcAddress not provided - should be auto-discovered + }) + assert.exists(synapse) + assert.exists(synapse.payments) + }) + + it('should accept optional genesisTimestamp for devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + const devnetAddresses = { + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + usdfcAddress: '0x2000000000000000000000000000000000000001', + genesisTimestamp: 1234567890, + } + const synapse = await Synapse.create({ + provider, + ...devnetAddresses, + }) + assert.exists(synapse) + assert.exists(synapse.payments) + }) + + it('should accept optional warmStorageViewAddress for devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + const devnetAddresses = { + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + warmStorageViewAddress: ADDRESSES.calibration.viewContract, + usdfcAddress: '0x2000000000000000000000000000000000000001', + } + const synapse = await Synapse.create({ + provider, + ...devnetAddresses, + }) + assert.exists(synapse) + assert.exists(synapse.storage) + assert.exists(synapse.payments) + }) + + it('should create instance with private key on devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + const devnetAddresses = { + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + usdfcAddress: '0x2000000000000000000000000000000000000001', + } + const synapse = await Synapse.create({ + privateKey: PRIVATE_KEYS.key1, + rpcURL: 'https://api.calibration.node.glif.io/rpc/v1', // use calibration RPC URL for devnet because the local RPC URL is not working in mocks + ...devnetAddresses, + }) + assert.exists(synapse) + assert.exists(synapse.payments) + assert.isTrue(synapse.payments instanceof PaymentsService) + }) + + it('should disable CDN and IPNI by default on devnet', async () => { + server.use( + JSONRPC({ + ...presets.basic, + eth_chainId: '0x1DF5E76', + }) + ) + const devnetAddresses = { + multicall3Address: '0x1000000000000000000000000000000000000001', + warmStorageAddress: ADDRESSES.calibration.warmStorage, + usdfcAddress: '0x2000000000000000000000000000000000000001', + } + const synapse = await Synapse.create({ + provider, + ...devnetAddresses, + }) + assert.exists(synapse) + assert.exists(synapse.storage) + // CDN and IPNI should be disabled for devnet (no external services available) + }) + }) + describe('StorageManager access', () => { it('should provide access to StorageManager via synapse.storage', async () => { server.use(JSONRPC(presets.basic)) diff --git a/packages/synapse-sdk/src/types.ts b/packages/synapse-sdk/src/types.ts index 359166c6..9178f367 100644 --- a/packages/synapse-sdk/src/types.ts +++ b/packages/synapse-sdk/src/types.ts @@ -22,7 +22,7 @@ export type ServiceProvider = string /** * Supported Filecoin network types */ -export type FilecoinNetworkType = 'mainnet' | 'calibration' +export type FilecoinNetworkType = 'mainnet' | 'calibration' | 'devnet' /** * Token identifier for balance queries @@ -67,6 +67,17 @@ export interface SynapseOptions { disableNonceManager?: boolean /** Override Warm Storage service contract address (defaults to network's default) */ warmStorageAddress?: string + /** Override Warm Storage view contract address (defaults to address discovered on-chain) */ + warmStorageViewAddress?: string + /** Override PDPVerifier contract address (defaults to network's default) */ + pdpVerifierAddress?: string + /** Override Multicall3 contract address (required for devnet) */ + multicall3Address?: string + /** Override USDFC token address (required for devnet) */ + usdfcAddress?: string + /** Override genesis timestamp for epoch calculations (recommended for devnet) */ + genesisTimestamp?: number + // Subgraph Integration (provide ONE of these options) /** Optional override for default subgraph service, to enable subgraph-based retrieval. */ subgraphService?: SubgraphRetrievalService diff --git a/packages/synapse-sdk/src/utils/constants.ts b/packages/synapse-sdk/src/utils/constants.ts index cf9fc446..35903605 100644 --- a/packages/synapse-sdk/src/utils/constants.ts +++ b/packages/synapse-sdk/src/utils/constants.ts @@ -21,6 +21,7 @@ export const TOKENS = { export const CHAIN_IDS: Record = { mainnet: 314, calibration: 314159, + devnet: 31415926, } as const /** @@ -130,6 +131,10 @@ export const GENESIS_TIMESTAMPS: Record = { * Calibration testnet genesis: November 1, 2022 18:13:00 UTC */ calibration: 1667326380, + /** + * Devnet genesis: Determined at runtime (placeholder value) + */ + devnet: 0, } as const /** @@ -324,6 +329,10 @@ export const RPC_URLS: Record, + // devnet: provided via options.warmStorageAddress + } as const satisfies Partial>, /** * Multicall3 contract addresses - used for batching multiple contract calls * Same address across most EVM chains including Filecoin + * Note: devnet addresses are determined at deployment time */ MULTICALL3: { mainnet: '0xcA11bde05977b3631167028862bE2a173976CA11', calibration: '0xcA11bde05977b3631167028862bE2a173976CA11', - } as const satisfies Record, + // devnet: provided via options.multicall3Address + } as const satisfies Partial>, USDFC: { mainnet: '0x80B98d3aa09ffff255c3ba4A241111Ff1262F045', calibration: '0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0', - } as const satisfies Record, + // devnet: provided via options.usdfcAddress + } as const satisfies Partial>, } as const diff --git a/packages/synapse-sdk/src/utils/epoch.ts b/packages/synapse-sdk/src/utils/epoch.ts index a5e21a7a..51719258 100644 --- a/packages/synapse-sdk/src/utils/epoch.ts +++ b/packages/synapse-sdk/src/utils/epoch.ts @@ -43,6 +43,28 @@ export function getGenesisTimestamp(network: FilecoinNetworkType): number { return GENESIS_TIMESTAMPS[network] } +/** + * Query the genesis timestamp from the blockchain by getting block 0 + * Useful for devnet where genesis timestamp is not known in advance + * @param provider - Ethers provider to query the blockchain + * @returns Promise resolving to genesis timestamp in seconds (Unix timestamp) + */ +export async function queryGenesisTimestamp(provider: ethers.Provider): Promise { + try { + const genesisBlock = await provider.getBlock(0) + if (!genesisBlock) { + throw createError('EpochUtils', 'queryGenesisTimestamp', 'Genesis block (block 0) not found') + } + return genesisBlock.timestamp + } catch (error) { + throw createError( + 'EpochUtils', + 'queryGenesisTimestamp', + `Failed to query genesis timestamp: ${error instanceof Error ? error.message : String(error)}` + ) + } +} + /** * Calculate the time until a future epoch * @param futureEpoch - The future epoch number diff --git a/packages/synapse-sdk/src/utils/network.ts b/packages/synapse-sdk/src/utils/network.ts index a0a42228..f0dba492 100644 --- a/packages/synapse-sdk/src/utils/network.ts +++ b/packages/synapse-sdk/src/utils/network.ts @@ -26,11 +26,13 @@ export async function getFilecoinNetworkType(provider: ethers.Provider): Promise return 'mainnet' } else if (chainId === CHAIN_IDS.calibration) { return 'calibration' + } else if (chainId === CHAIN_IDS.devnet) { + return 'devnet' } else { throw createError( 'NetworkUtils', 'getFilecoinNetworkType', - `Unsupported network: chain ID ${chainId}. Only Filecoin mainnet (${CHAIN_IDS.mainnet}) and calibration (${CHAIN_IDS.calibration}) are supported.` + `Unsupported network: chain ID ${chainId}. Only Filecoin mainnet (${CHAIN_IDS.mainnet}), calibration (${CHAIN_IDS.calibration}), and devnet (${CHAIN_IDS.devnet}) are supported.` ) } } catch (error) { diff --git a/packages/synapse-sdk/src/warm-storage/service.ts b/packages/synapse-sdk/src/warm-storage/service.ts index 2885da7b..0041d980 100644 --- a/packages/synapse-sdk/src/warm-storage/service.ts +++ b/packages/synapse-sdk/src/warm-storage/service.ts @@ -112,6 +112,7 @@ export class WarmStorageService { serviceProviderRegistry: string sessionKeyRegistry: string } + private readonly _multicall3Address: string /** * Private constructor - use WarmStorageService.create() instead @@ -119,6 +120,7 @@ export class WarmStorageService { private constructor( provider: ethers.Provider, warmStorageAddress: string, + multicall3Address: string, addresses: { pdpVerifier: string payments: string @@ -132,21 +134,36 @@ export class WarmStorageService { this._provider = provider this._warmStorageAddress = warmStorageAddress this._addresses = addresses + this._multicall3Address = multicall3Address } /** * Create a new WarmStorageService instance with initialized addresses */ - static async create(provider: ethers.Provider, warmStorageAddress: string): Promise { + static async create( + provider: ethers.Provider, + warmStorageAddress: string, + multicall3Address: string | null = null, + overrideViewAddress: string | null = null + ): Promise { // Get network from provider and validate it's a supported Filecoin network const networkName = await getFilecoinNetworkType(provider) + // Resolve Multicall3 address (runtime override or constants) + const resolvedMulticallAddress = + multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[networkName as keyof typeof CONTRACT_ADDRESSES.MULTICALL3] + + if (!resolvedMulticallAddress) { + throw createError( + 'WarmStorageService', + 'create', + `No Multicall3 address configured for network: ${networkName}. ` + + 'Provide multicall3Address when initializing in devnet.' + ) + } + // Initialize all contract addresses using Multicall3 - const multicall = new ethers.Contract( - CONTRACT_ADDRESSES.MULTICALL3[networkName], - CONTRACT_ABIS.MULTICALL3, - provider - ) + const multicall = new ethers.Contract(resolvedMulticallAddress, CONTRACT_ABIS.MULTICALL3, provider) const iface = new ethers.Interface(CONTRACT_ABIS.WARM_STORAGE) @@ -195,18 +212,22 @@ export class WarmStorageService { payments: iface.decodeFunctionResult('paymentsContractAddress', results[1].returnData)[0], usdfcToken: iface.decodeFunctionResult('usdfcTokenAddress', results[2].returnData)[0], filBeamBeneficiary: iface.decodeFunctionResult('filBeamBeneficiaryAddress', results[3].returnData)[0], - viewContract: iface.decodeFunctionResult('viewContractAddress', results[4].returnData)[0], + viewContract: overrideViewAddress ?? iface.decodeFunctionResult('viewContractAddress', results[4].returnData)[0], serviceProviderRegistry: iface.decodeFunctionResult('serviceProviderRegistry', results[5].returnData)[0], sessionKeyRegistry: iface.decodeFunctionResult('sessionKeyRegistry', results[6].returnData)[0], } - return new WarmStorageService(provider, warmStorageAddress, addresses) + return new WarmStorageService(provider, warmStorageAddress, resolvedMulticallAddress, addresses) } getPDPVerifierAddress(): string { return this._addresses.pdpVerifier } + getMulticall3Address(): string { + return this._multicall3Address + } + getPaymentsAddress(): string { return this._addresses.payments } @@ -227,6 +248,10 @@ export class WarmStorageService { return this._addresses.sessionKeyRegistry } + getFilBeamBeneficiaryAddress(): string { + return this._addresses.filBeamBeneficiary + } + /** * Get the provider instance * @returns The ethers provider diff --git a/utils/post-deploy-setup.js b/utils/post-deploy-setup.js index c8c04e4c..17a652cc 100644 --- a/utils/post-deploy-setup.js +++ b/utils/post-deploy-setup.js @@ -81,7 +81,7 @@ * * - WARM_STORAGE_CONTRACT_ADDRESS: Warm Storage address (defaults to address in constants.ts for network) * - SP_REGISTRY_ADDRESS: ServiceProviderRegistry address (auto-discovered from WarmStorage if not provided) - * - NETWORK: Either 'mainnet' or 'calibration' (default: calibration) + * - NETWORK: Either 'mainnet', 'calibration', or 'devnet' (default: calibration) * - RPC_URL: Custom RPC endpoint (overrides default network RPC) * - SP_NAME: Provider name (default: "Test Service Provider") * - SP_DESCRIPTION: Provider description (default: "Test provider for Warm Storage") @@ -433,8 +433,8 @@ async function main() { const customRpcUrl = process.env.RPC_URL // Validate network - if (network !== 'mainnet' && network !== 'calibration') { - error('NETWORK must be either "mainnet" or "calibration"') + if (network !== 'mainnet' && network !== 'calibration' && network !== 'devnet') { + error('NETWORK must be either "mainnet", "calibration", or "devnet"') process.exit(1) } diff --git a/utils/sp-tool.js b/utils/sp-tool.js index 0dfd9f6d..adfceebe 100644 --- a/utils/sp-tool.js +++ b/utils/sp-tool.js @@ -766,7 +766,7 @@ WarmStorage Commands: warm-list List WarmStorage approved providers Options: - --network Network to use: 'mainnet' or 'calibration' (default: calibration) + --network Network to use: 'mainnet', 'calibration', or 'devnet' (default: calibration) --rpc-url RPC endpoint (overrides network default) --key Private key for signing (required for write operations) --registry
Registry contract address (overrides discovery) @@ -835,8 +835,8 @@ Examples: // Setup provider based on network flag const network = options.network || 'calibration' - if (network !== 'mainnet' && network !== 'calibration') { - console.error(`Error: Invalid network '${network}'. Must be 'mainnet' or 'calibration'`) + if (network !== 'mainnet' && network !== 'calibration' && network !== 'devnet') { + console.error(`Error: Invalid network '${network}'. Must be 'mainnet', 'calibration', or 'devnet'`) process.exit(1) }