diff --git a/docs/src/content/docs/developer-guides/devnet.md b/docs/src/content/docs/developer-guides/devnet.md new file mode 100644 index 00000000..fb2faff1 --- /dev/null +++ b/docs/src/content/docs/developer-guides/devnet.md @@ -0,0 +1,41 @@ +--- +title: Devnet Configuration +description: Configure Synapse SDK for local development +sidebar: + order: 10 +--- + +The Synapse SDK supports local development networks in addition to mainnet and calibration. + +## Network Support + +| Network | Chain ID | Auto-Discovery | +|---------|----------|----------------| +| mainnet | 314 | Yes | +| calibration | 314159 | Yes | +| devnet | 31415926 | No | + +For **mainnet** and **calibration**, contract addresses are auto-discovered. For **devnet**, you must provide addresses explicitly. + +## Required Options + +When connecting to a devnet, provide these options: + +```ts +import { Synapse } from '@filoz/synapse-sdk' + +const synapse = await Synapse.create({ + privateKey: process.env.PRIVATE_KEY, + rpcURL: process.env.RPC_URL, + warmStorageAddress: process.env.WARM_STORAGE_ADDRESS, + multicall3Address: process.env.MULTICALL3_ADDRESS, + // Optional: override USDFC token address + usdfcAddress: process.env.USDFC_ADDRESS, +}) +``` + +All other contract addresses (Payments, PDPVerifier, ServiceProviderRegistry, SessionKeyRegistry) are discovered automatically from `warmStorageAddress`. + +## Local Development + +For local development and integration testing, see [foc-localnet](https://github.com/FilOzone/foc-localnet) which provides a complete local Filecoin network with all FOC contracts deployed. diff --git a/packages/synapse-core/src/chains.ts b/packages/synapse-core/src/chains.ts index 0c07e0f8..f19a0312 100644 --- a/packages/synapse-core/src/chains.ts +++ b/packages/synapse-core/src/chains.ts @@ -198,6 +198,68 @@ export const calibration: Chain = { testnet: true, } +/** + * Filecoin Devnet + * + * Local development network. Contract addresses must be provided by the devnet deployment. + */ +export const devnet: Chain = { + id: 31415926, + name: 'Filecoin - Devnet', + nativeCurrency: { + name: 'Filecoin', + symbol: 'FIL', + decimals: 18, + }, + rpcUrls: { + default: { + http: ['http://127.0.0.1:5700/rpc/v1'], + webSocket: ['ws://127.0.0.1:5700/rpc/v1'], + }, + }, + blockExplorers: { + default: { + name: 'Local Blockscout', + url: 'http://localhost:8080', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 0, + }, + usdfc: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.erc20WithPermit, + }, + payments: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.payments, + }, + storage: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.storage, + }, + storageView: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.storageView, + }, + serviceProviderRegistry: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.serviceProviderRegistry, + }, + sessionKeyRegistry: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.sessionKeyRegistry, + }, + pdp: { + address: '0x0000000000000000000000000000000000000000', + abi: Abis.pdp, + }, + }, + testnet: true, +} + /** * Get a chain by id * @@ -213,6 +275,8 @@ export function getChain(id?: number): Chain { return mainnet case 314159: return calibration + case 31415926: + return devnet default: throw new Error(`Chain with id ${id} not found`) } diff --git a/packages/synapse-sdk/src/filbeam/service.ts b/packages/synapse-sdk/src/filbeam/service.ts index 3b7853c4..e4bf459a 100644 --- a/packages/synapse-sdk/src/filbeam/service.ts +++ b/packages/synapse-sdk/src/filbeam/service.ts @@ -59,12 +59,12 @@ export class FilBeamService { } private _validateNetworkType(network: FilecoinNetworkType) { - if (network === 'mainnet' || network === 'calibration') return + if (network === 'mainnet' || network === 'calibration' || network === 'devnet') return throw createError( 'FilBeamService', 'validateNetworkType', - 'Unsupported network type: Only Filecoin mainnet and calibration networks are supported.' + 'Unsupported network type: Only Filecoin mainnet, calibration, and devnet networks are supported.' ) } diff --git a/packages/synapse-sdk/src/payments/service.ts b/packages/synapse-sdk/src/payments/service.ts index 58ece069..fe0a0840 100644 --- a/packages/synapse-sdk/src/payments/service.ts +++ b/packages/synapse-sdk/src/payments/service.ts @@ -37,6 +37,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 @@ -57,13 +58,15 @@ export class PaymentsService { signer: ethers.Signer, paymentsAddress: string, usdfcAddress: string, - disableNonceManager: boolean + disableNonceManager: boolean, + multicall3Address: string | null ) { this._provider = provider this._signer = signer this._paymentsAddress = paymentsAddress this._usdfcAddress = usdfcAddress this._disableNonceManager = disableNonceManager + this._multicall3Address = multicall3Address } /** @@ -105,7 +108,10 @@ 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] + if (!multicall3Address) { + throw createError('PaymentsService', contextName, `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..03202d34 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 ) { 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 { @@ -77,12 +80,12 @@ export class SessionKey { */ async fetchExpiries(permissions: string[] = PDP_PERMISSIONS): Promise> { const network = await getFilecoinNetworkType(this._provider) + const multicall3Address = this._multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[network] + if (!multicall3Address) { + throw new Error(`No Multicall3 address available for network: ${network}`) + } - const multicall = new ethers.Contract( - CONTRACT_ADDRESSES.MULTICALL3[network], - CONTRACT_ABIS.MULTICALL3, - this._provider - ) + 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..750a29f8 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,10 @@ 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] + 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 9be717d6..d5da8254 100644 --- a/packages/synapse-sdk/src/synapse.ts +++ b/packages/synapse-sdk/src/synapse.ts @@ -37,6 +37,7 @@ export class Synapse { private readonly _storageManager: StorageManager private readonly _filbeamService: FilBeamService private _session: SessionKey | null = null + private readonly _multicall3Address: string /** * Create a new Synapse instance with async initialization. @@ -119,31 +120,45 @@ 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 multicall3Address = options.multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[network] + if (!multicall3Address) { + 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}`) + 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, warmStorageAddress, multicall3Address) // 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, + multicall3Address ) // Create SPRegistryService for use in retrievers const registryAddress = warmStorageService.getServiceProviderRegistryAddress() - const spRegistry = new SPRegistryService(provider, registryAddress) + const spRegistry = new SPRegistryService(provider, registryAddress, multicall3Address) // Initialize piece retriever (use provided or create default) let pieceRetriever: PieceRetriever @@ -185,7 +200,8 @@ export class Synapse { pieceRetriever, filbeamService, options.dev === false, - options.withIpni + options.withIpni, + multicall3Address ) } @@ -201,7 +217,8 @@ export class Synapse { pieceRetriever: PieceRetriever, filbeamService: FilBeamService, dev: boolean, - withIpni?: boolean + withIpni: boolean | undefined, + multicall3Address: string ) { this._signer = signer this._provider = provider @@ -213,6 +230,7 @@ export class Synapse { this._warmStorageAddress = warmStorageAddress this._filbeamService = filbeamService this._session = null + this._multicall3Address = multicall3Address // Initialize StorageManager this._storageManager = new StorageManager( @@ -280,7 +298,8 @@ export class Synapse { this._provider, this._warmStorageService.getSessionKeyRegistryAddress(), sessionKeySigner, - this._signer + this._signer, + this._multicall3Address ) } @@ -321,7 +340,12 @@ export class Synapse { * @returns The numeric chain ID */ getChainId(): number { - return this._network === 'mainnet' ? CHAIN_IDS.mainnet : CHAIN_IDS.calibration + if (this._network === 'mainnet') { + return CHAIN_IDS.mainnet + } else if (this._network === 'calibration') { + return CHAIN_IDS.calibration + } + return CHAIN_IDS.devnet } /** @@ -452,7 +476,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/payments.test.ts b/packages/synapse-sdk/src/test/payments.test.ts index 9936a73b..50d4b0e2 100644 --- a/packages/synapse-sdk/src/test/payments.test.ts +++ b/packages/synapse-sdk/src/test/payments.test.ts @@ -33,7 +33,7 @@ describe('PaymentsService', () => { server.resetHandlers() provider = new ethers.JsonRpcProvider('https://api.calibration.node.glif.io/rpc/v1') signer = new ethers.Wallet(Mocks.PRIVATE_KEYS.key1, provider) - payments = new PaymentsService(provider, signer, paymentsAddress, usdfcAddress, false) + payments = new PaymentsService(provider, signer, paymentsAddress, usdfcAddress, false, null) }) describe('Instantiation', () => { diff --git a/packages/synapse-sdk/src/test/warm-storage-service.test.ts b/packages/synapse-sdk/src/test/warm-storage-service.test.ts index e7da1dd5..9c6ac1e3 100644 --- a/packages/synapse-sdk/src/test/warm-storage-service.test.ts +++ b/packages/synapse-sdk/src/test/warm-storage-service.test.ts @@ -43,7 +43,8 @@ describe('WarmStorageService', () => { signer, Mocks.ADDRESSES.calibration.payments, Mocks.ADDRESSES.calibration.usdfcToken, - false + false, + null ) server.resetHandlers() }) diff --git a/packages/synapse-sdk/src/types.ts b/packages/synapse-sdk/src/types.ts index d65d4482..9827ab05 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,10 @@ export interface SynapseOptions { disableNonceManager?: boolean /** Override Warm Storage service contract address (defaults to network's default) */ warmStorageAddress?: string + /** Override Multicall3 contract address (required for devnet) */ + multicall3Address?: string + /** Override USDFC token address (optional, useful for devnet) */ + usdfcAddress?: string // 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 d9e4c46c..95b9a08b 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,12 @@ export const GENESIS_TIMESTAMPS: Record = { * Calibration testnet genesis: November 1, 2022 18:13:00 UTC */ calibration: 1667326380, + /** + * Devnet genesis: Set to 0 as placeholder. Epoch<>Date conversions (epochToDate, + * dateToEpoch) will return incorrect results on devnet. Core contract operations + * are unaffected as they use epochs directly. + */ + devnet: 0, // TODO: Consider adding override in SynapseOptions if/when this is a problem } as const /** @@ -314,6 +321,10 @@ export const RPC_URLS: Record, + devnet: undefined, + } as const satisfies Record, /** * Multicall3 contract addresses - used for batching multiple contract calls @@ -336,10 +348,12 @@ export const CONTRACT_ADDRESSES = { MULTICALL3: { mainnet: '0xcA11bde05977b3631167028862bE2a173976CA11', calibration: '0xcA11bde05977b3631167028862bE2a173976CA11', - } as const satisfies Record, + devnet: undefined, + } as const satisfies Record, USDFC: { mainnet: '0x80B98d3aa09ffff255c3ba4A241111Ff1262F045', calibration: '0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0', - } as const satisfies Record, + devnet: undefined, + } as const satisfies Record, } as const diff --git a/packages/synapse-sdk/src/utils/network.ts b/packages/synapse-sdk/src/utils/network.ts index a0a42228..6e612e89 100644 --- a/packages/synapse-sdk/src/utils/network.ts +++ b/packages/synapse-sdk/src/utils/network.ts @@ -26,13 +26,15 @@ export async function getFilecoinNetworkType(provider: ethers.Provider): Promise return 'mainnet' } else if (chainId === CHAIN_IDS.calibration) { return 'calibration' - } else { - throw createError( - 'NetworkUtils', - 'getFilecoinNetworkType', - `Unsupported network: chain ID ${chainId}. Only Filecoin mainnet (${CHAIN_IDS.mainnet}) and calibration (${CHAIN_IDS.calibration}) are supported.` - ) + } else if (chainId === CHAIN_IDS.devnet) { + return 'devnet' } + + throw createError( + 'NetworkUtils', + 'getFilecoinNetworkType', + `Unsupported network: chain ID ${chainId}. Only Filecoin mainnet (${CHAIN_IDS.mainnet}), calibration (${CHAIN_IDS.calibration}), and devnet (${CHAIN_IDS.devnet}) are supported.` + ) } catch (error) { if (error instanceof Error && error.message.includes('Unsupported network')) { throw error // Re-throw our own error diff --git a/packages/synapse-sdk/src/warm-storage/service.ts b/packages/synapse-sdk/src/warm-storage/service.ts index 9346c9c3..4086cf33 100644 --- a/packages/synapse-sdk/src/warm-storage/service.ts +++ b/packages/synapse-sdk/src/warm-storage/service.ts @@ -107,6 +107,7 @@ export class WarmStorageService { private _warmStorageContract: ethers.Contract | null = null private _warmStorageViewContract: ethers.Contract | null = null private _pdpVerifier: PDPVerifier | null = null + private readonly _multicall3Address: string // All discovered addresses private readonly _addresses: { @@ -125,6 +126,7 @@ export class WarmStorageService { private constructor( provider: ethers.Provider, warmStorageAddress: string, + multicall3Address: string, addresses: { pdpVerifier: string payments: string @@ -137,22 +139,32 @@ export class WarmStorageService { ) { this._provider = provider this._warmStorageAddress = warmStorageAddress + this._multicall3Address = multicall3Address this._addresses = addresses } /** * 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 + ): Promise { // Get network from provider and validate it's a supported Filecoin network const networkName = await getFilecoinNetworkType(provider) + const resolvedMulticallAddress = multicall3Address ?? CONTRACT_ADDRESSES.MULTICALL3[networkName] + if (!resolvedMulticallAddress) { + throw createError( + 'WarmStorageService', + 'create', + `No Multicall3 address configured for network: ${networkName}. Provide multicall3Address when initializing.` + ) + } + // 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) @@ -206,7 +218,11 @@ export class WarmStorageService { sessionKeyRegistry: iface.decodeFunctionResult('sessionKeyRegistry', results[6].returnData)[0], } - return new WarmStorageService(provider, warmStorageAddress, addresses) + return new WarmStorageService(provider, warmStorageAddress, resolvedMulticallAddress, addresses) + } + + getMulticall3Address(): string { + return this._multicall3Address } getPDPVerifierAddress(): string { diff --git a/utils/example-storage-e2e.js b/utils/example-storage-e2e.js index e53f3157..cd42a90b 100644 --- a/utils/example-storage-e2e.js +++ b/utils/example-storage-e2e.js @@ -12,7 +12,11 @@ * Required environment variables: * - PRIVATE_KEY: Your Ethereum private key (with 0x prefix) * - RPC_URL: Filecoin RPC endpoint (defaults to calibration) - * - WARM_STORAGE_ADDRESS: Warm Storage service contract address (optional, uses default for network) + * + * Optional environment variables (for devnet): + * - WARM_STORAGE_ADDRESS: Warm Storage service contract address (uses default for network) + * - MULTICALL3_ADDRESS: Multicall3 address (required for devnet) + * - USDFC_ADDRESS: USDFC token address (optional) * * Usage: * PRIVATE_KEY=0x... node example-storage-e2e.js [file-path2] [file-path3] ... @@ -31,6 +35,8 @@ import { const PRIVATE_KEY = process.env.PRIVATE_KEY const RPC_URL = process.env.RPC_URL || 'https://api.calibration.node.glif.io/rpc/v1' const WARM_STORAGE_ADDRESS = process.env.WARM_STORAGE_ADDRESS // Optional - will use default for network +const MULTICALL3_ADDRESS = process.env.MULTICALL3_ADDRESS // Required for devnet +const USDFC_ADDRESS = process.env.USDFC_ADDRESS // Optional function printUsageAndExit() { console.error('Usage: PRIVATE_KEY=0x... node example-storage-e2e.js [file-path2] ...') @@ -98,15 +104,22 @@ async function main() { console.log(`RPC URL: ${RPC_URL}`) const synapseOptions = { + multicall3Address: MULTICALL3_ADDRESS, privateKey: PRIVATE_KEY, rpcURL: RPC_URL, + usdfcAddress: USDFC_ADDRESS, + warmStorageAddress: WARM_STORAGE_ADDRESS, } - // Add Warm Storage address if provided if (WARM_STORAGE_ADDRESS) { - synapseOptions.warmStorageAddress = WARM_STORAGE_ADDRESS console.log(`Warm Storage Address: ${WARM_STORAGE_ADDRESS}`) } + if (MULTICALL3_ADDRESS) { + console.log(`Multicall3 Address: ${MULTICALL3_ADDRESS}`) + } + if (USDFC_ADDRESS) { + console.log(`USDFC Address: ${USDFC_ADDRESS}`) + } const synapse = await Synapse.create(synapseOptions) console.log('āœ“ Synapse instance created') diff --git a/utils/post-deploy-setup.js b/utils/post-deploy-setup.js index c8c04e4c..06a1f149 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") @@ -118,6 +118,24 @@ function parseArgs() { if (args[i] === '--mode' && args[i + 1]) { options.mode = args[i + 1] i++ + } else if (args[i] === '--network' && args[i + 1]) { + options.network = args[i + 1] + i++ + } else if (args[i] === '--rpc-url' && args[i + 1]) { + options.rpcUrl = args[i + 1] + i++ + } else if (args[i] === '--warm-storage' && args[i + 1]) { + options.warmStorageAddress = args[i + 1] + i++ + } else if (args[i] === '--multicall3' && args[i + 1]) { + options.multicall3Address = args[i + 1] + i++ + } else if (args[i] === '--usdfc' && args[i + 1]) { + options.usdfcAddress = args[i + 1] + i++ + } else if (args[i] === '--sp-registry' && args[i + 1]) { + options.spRegistryAddress = args[i + 1] + i++ } } @@ -128,6 +146,13 @@ function parseArgs() { process.exit(1) } + // Validate network + const validNetworks = ['mainnet', 'calibration', 'devnet'] + if (options.network && !validNetworks.includes(options.network)) { + error(`Invalid network: ${options.network}. Must be one of: ${validNetworks.join(', ')}`) + process.exit(1) + } + return options } @@ -335,18 +360,31 @@ async function setupProvider(deployerSigner, spSigner, provider, warmStorage, sp } // Setup client function -async function setupClient(clientSigner, provider, warmStorage, warmStorageAddress) { +async function setupClient( + clientSigner, + provider, + warmStorage, + warmStorageAddress, + usdfcAddressOverride = null, + multicall3Address = null +) { // === Set up client payment approvals === log('\nšŸ’° Client Payment Setup') - // USDFC token address on calibration network - // This is a standard token address across all deployments - const usdfcAddress = '0xb3042734b608a1B16e9e86B374A3f3e389B4cDf0' - log(`USDFC token address: ${usdfcAddress}`) + // Get USDFC token address (use override if provided, otherwise auto-discover from WarmStorage) + const usdfcAddress = usdfcAddressOverride ?? warmStorage.getUSDFCTokenAddress() + log(`USDFC token address: ${usdfcAddress}${usdfcAddressOverride ? ' (override)' : ' (auto-discovered)'}`) // Create PaymentsService const paymentsAddress = await warmStorage.getPaymentsAddress() - const paymentsService = new PaymentsService(provider, clientSigner, paymentsAddress, usdfcAddress) + const paymentsService = new PaymentsService( + provider, + clientSigner, + paymentsAddress, + usdfcAddress, + false, + multicall3Address + ) // Check client's USDFC balance const clientBalance = await paymentsService.walletBalance(TOKENS.USDFC) @@ -414,6 +452,7 @@ async function main() { const mode = args.mode || 'client' // Default to client mode log(`šŸš€ Running post-deploy setup in '${mode}' mode`) + log(`Arguments: ${JSON.stringify(args)}`) // Get environment variables based on mode let deployerPrivateKey, spPrivateKey, clientPrivateKey, spServiceUrl @@ -429,24 +468,32 @@ async function main() { } // Common configuration - const network = process.env.NETWORK || 'calibration' - const customRpcUrl = process.env.RPC_URL - - // Validate network - if (network !== 'mainnet' && network !== 'calibration') { - error('NETWORK must be either "mainnet" or "calibration"') - process.exit(1) - } + const network = args.network || process.env.NETWORK || 'calibration' + const customRpcUrl = args.rpcUrl || process.env.RPC_URL // Get RPC URL const rpcURL = customRpcUrl || RPC_URLS[network].http + // Get Multicall3 address - use provided or default from constants + const multicall3Address = + args.multicall3Address || process.env.MULTICALL3_ADDRESS || CONTRACT_ADDRESSES.MULTICALL3[network] + + // Get USDFC address override (optional, will auto-discover from WarmStorage if not provided) + const usdfcAddressOverride = args.usdfcAddress || process.env.USDFC_ADDRESS || null + // Get WarmStorage address - use provided or default from constants - let warmStorageAddress = process.env.WARM_STORAGE_CONTRACT_ADDRESS + let warmStorageAddress = args.warmStorageAddress || process.env.WARM_STORAGE_CONTRACT_ADDRESS if (!warmStorageAddress) { - warmStorageAddress = CONTRACT_ADDRESSES.WARM_STORAGE[network] + // For devnet, check environment variable + if (network === 'devnet') { + warmStorageAddress = process.env.DEVNET_WARM_STORAGE_ADDRESS + } else { + warmStorageAddress = CONTRACT_ADDRESSES.WARM_STORAGE[network] + } if (!warmStorageAddress) { - error(`No default Warm Storage address for ${network} network. Please provide WARM_STORAGE_CONTRACT_ADDRESS.`) + error( + `No default Warm Storage address for ${network} network. Please provide WARM_STORAGE_CONTRACT_ADDRESS${network === 'devnet' ? ' or DEVNET_WARM_STORAGE_ADDRESS' : ''}.` + ) process.exit(1) } log(`Using default Warm Storage address from constants.ts: ${warmStorageAddress}`) @@ -454,6 +501,7 @@ async function main() { log(`Starting post-deployment setup for network: ${network}`) log(`Warm Storage contract address: ${warmStorageAddress}`) + log(`Multicall3 address: ${multicall3Address || 'auto-detect'}`) log(`Using RPC: ${rpcURL}`) // Create provider with extended timeout for Filecoin's 30s block time @@ -467,7 +515,7 @@ async function main() { provider._getConnection().timeout = 120000 // 2 minutes // Create WarmStorage service - const warmStorage = await WarmStorageService.create(provider, warmStorageAddress) + const warmStorage = await WarmStorageService.create(provider, warmStorageAddress, multicall3Address) // Variables to track what was setup let providerId = null @@ -505,7 +553,7 @@ async function main() { log(` Storage Price: ${ethers.formatUnits(storagePricePerTibPerMonth, 18)} USDFC/TiB/month`) // Get registry address - use provided or discover from WarmStorage - let spRegistryAddress = process.env.SP_REGISTRY_ADDRESS + let spRegistryAddress = args.spRegistryAddress || process.env.SP_REGISTRY_ADDRESS if (spRegistryAddress) { log(`Using provided ServiceProviderRegistry address: ${spRegistryAddress}`) } else { @@ -545,7 +593,14 @@ async function main() { log(`\nClient address: ${clientAddress}`) - await setupClient(clientSigner, provider, warmStorage, warmStorageAddress) + await setupClient( + clientSigner, + provider, + warmStorage, + warmStorageAddress, + usdfcAddressOverride, + multicall3Address + ) } // === Summary ===