Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/src/content/docs/developer-guides/devnet.md
Original file line number Diff line number Diff line change
@@ -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.
64 changes: 64 additions & 0 deletions packages/synapse-core/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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`)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/synapse-sdk/src/filbeam/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
)
}

Expand Down
10 changes: 8 additions & 2 deletions packages/synapse-sdk/src/payments/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

/**
Expand Down Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions packages/synapse-sdk/src/session/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -77,12 +80,12 @@ export class SessionKey {
*/
async fetchExpiries(permissions: string[] = PDP_PERMISSIONS): Promise<Record<string, bigint>> {
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([
Expand Down
9 changes: 7 additions & 2 deletions packages/synapse-sdk/src/sp-registry/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -453,7 +455,10 @@ export class SPRegistryService {
*/
private async _getProvidersWithMulticall(providerIds: number[]): Promise<ProviderInfo[]> {
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)

Expand Down
48 changes: 36 additions & 12 deletions packages/synapse-sdk/src/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -185,7 +200,8 @@ export class Synapse {
pieceRetriever,
filbeamService,
options.dev === false,
options.withIpni
options.withIpni,
multicall3Address
)
}

Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -280,7 +298,8 @@ export class Synapse {
this._provider,
this._warmStorageService.getSessionKeyRegistryAddress(),
sessionKeySigner,
this._signer
this._signer,
this._multicall3Address
)
}

Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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') {
Expand Down
2 changes: 1 addition & 1 deletion packages/synapse-sdk/src/test/payments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ describe('WarmStorageService', () => {
signer,
Mocks.ADDRESSES.calibration.payments,
Mocks.ADDRESSES.calibration.usdfcToken,
false
false,
null
)
server.resetHandlers()
})
Expand Down
Loading