diff --git a/EIPS/eip-7876.md b/EIPS/eip-7876.md new file mode 100644 index 00000000000000..d9e8bc55c742bc --- /dev/null +++ b/EIPS/eip-7876.md @@ -0,0 +1,586 @@ +--- +eip: 7876 +title: Ethereum Network Configuration for DApps +description: EVM-compatible network configuration specification for DApps supporting any custom network features through extensions. +author: Bogdan Gusiev (@bogdan), Sergey Bomko (@aquiladev) +discussions-to: https://ethereum-magicians.org/t/eip-7876-unified-network-configuration/22763 +status: Draft +type: Informational +created: 2025-01-03 +requires: 55 +--- + +## Abstract + +This standard defines a universal format for specifying network configurations to decentralized applications (DApps). The configuration includes essential information about Ethereum networks, such as available RPC URLs, native currencies, contract addresses, and block explorers. Configurations are intended to be provided by: + +- Network maintainers to make sure their network is supported by DApp. +- Smart contract developers to ensure the smart contracts are properly integrated into DApps and changes are consistently delivered. +- DApps developers internally in the organisation to make use of software libraries designed around the standard and maintain consistent configuration across multiple platforms (e.g iOS, Android). + +The goal of this standard is to simplify network configuration for DApp developers and enhance interoperability across Ethereum-compatible networks. + +Given that different EVM-compatible chains introduce unique features, this standard also supports extensibility, allowing networks to specify additional configuration parameters beyond the base requirements. This flexibility prevents networks from resorting to external storage for their unique configurations, preserving the goal of a unified and comprehensive network configuration standard. + +## Motivation + +Currently, decentralized applications (DApps) support numerous EVM-compatible networks and often define network configurations in inconsistent formats, which complicates interoperability and sharing of configurations. This standard introduces a **unified network configuration** format that can be adopted by EVM-based networks, making it easier for developers to integrate with multiple networks and providing consistent configuration management. + +The proposed configuration format includes essential details required by DApps, including: + +- Network name and ID +- RPC URL for Ethereum nodes +- Native currency details +- Smart contract addresses +- Available block explorers and NFT marketplaces + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +The configuration **MUST** use a JSON object, which DApp developers can use to define multiple Ethereum networks. + +### Interface definition + +The specification interface is provided in the TypeScript type definition format, which is widely used to define JSON file formats for Ethereum DApps: +Refer to [Example Configuration](#example-configuration) for an illustrative implementation of this standard. + +```typescript +/** + * Represents the configuration for a smart contract. + * + * @typedef ContractConfig + * @property address - The address of the contract on the Ethereum network. **MUST** use a checksum. + * @property abiUrl - URL to contract ABI file, can be relative to `abiRoot` if specified or absolute. The absence of the key or `null` value indicates that the ABI of the contract is unknown. URL content **MUST** use Contract ABI specification JSON format. + * @property blockCreated - The block number in which the contract was deployed. + * @see https://docs.soliditylang.org/en/latest/abi-spec.html#json + * @see ./eip-55.md + */ +type ContractConfig = { + address: Address, + abiUrl?: string | null, + blockCreated: number, +}; + +/** + * Represents a list of contract names. No standard contracts are defined. + * The list may vary from one DApp to another for the same network. + * @typedef ContractName + */ +type ContractName = string; + +/** + * Represents the configuration for block explorers in the network configuration. + * The URLs for address, transaction, and NFT lookups are **relative** to the `root` URL. + * The `block`, `address`, `tx`, and `nft` URLs **MUST** be relative to the `root` URL. + * URLs must include a special parameters, like `:block`, `:address`, `:tx`, `:token` for relevant properties (see examples). + * + * @typedef ExplorerConfig + * @property root - The root URL of the block explorer (e.g., `https://freescan.example.com`). + * @property block - The relative URL path for viewing a specific block (e.g., `/block/:block`). If not available, this can be `null`. + * @property address - The relative URL path for viewing a specific address (e.g., `/address/:address`). If not available, this can be `null`. + * @property tx - The relative URL path for viewing a transaction (e.g., `/tx/:tx`). If not available, this can be `null`. + * @property nft - The relative URL path for viewing a specific NFT (e.g., `/nft/:address/:token`). If not available, this can be `null`. + */ +type ExplorerConfig = { + root: string; + address: string | null; + tx: string | null; + nft: string | null; + block: string | null; +}; + + +/** + * Represents RPC node endpoint available in the network. + * + * @typedef RpcConfig + * @property url - The URL under which the RPC is available (e.g. `https://public-node.example.com/rpc`). + */ +type RpcConfig = { + url: string +} + +/** + * Represents relation of the network to other networks. + * e.g. a network is a testnet of a mainnet, a network is Layer 2 above some other network, + * network has a bridge to other network etc. + * + * @typedef RpcConfig + * @property mainnetChainId - Chain ID of the recommended mainnet network if current network is a testnet. MUST be `null` for mainnet. + * @property parentChainId - Chain ID of a network that current network is built on top of (e.g. if current network is L2, it will be L1 chain id) + */ +type RelationsConfig = { + mainnetChainId: number | null, + parentChainId: number | null, +}; + +/** + * Represents the configuration for the native currency used in the network. + * + * @typedef NativeCurrencyConfig + * @property name - The name of the native currency (e.g., "Ether"). + * @property symbol - The symbol of the native currency (e.g., "ETH"). + * @property decimals - The number of decimal places the currency uses (e.g., 18 for Ether). + */ +type NativeCurrencyConfig = { + name: string, + symbol: string, + decimals: number, +}; + +/** + * Represents the configuration for a specific Ethereum network used in the DApp. + * This includes details such as the network name, testnet status, RPC configurations, + * explorers, and smart contracts related to the network. + * + * @typedef NetworkConfig + * @property name - The name of the network (e.g., "Ethereum", "Base"). + * @property testnet - A flag indicating whether the network is a testnet (`true`) or mainnet (`false`). + * @property nativeCurrency - Configuration for the network's native currency (e.g., ETH). + * @property relations - Relationships with other networks (e.g., parent network or L1/L2 or other bridged networks). + * @property rpcs - A record of RPC configurations for different nodes in the network, where keys represent the node names (e.g., "publicnode"). + * @property explorers - Optional record for block explorer configurations (e.g., nft marketplaces). + * @property abiRoot - Optional root URL for the ABI (Application Binary Interface) if it is hosted externally. + * @property contracts - A record of contract configurations where the values may be `null` if the contract is not deployed. + */ +type NetworkConfig = { + name: string, + testnet: boolean, + nativeCurrency: NativeCurrencyConfig, + relations: RelationsConfig; + rpcs: Record, + explorers?: Record, + abiRoot?: string, + contracts: Record; +}; + +/** + * Represents the configuration for a DApp, including version information, a summary, + * an optional description, and the network configurations. + * + * @typedef {Object} Configuration + * @property version - The version of the configuration, typically following semantic versioning (e.g., "1.0.0"). + * @property timestamp - The timestamp in ISO8601 compatible format of when the configuration was created or last updated (e.g., "2025-01-01T12:00:00Z"). + * @property summary - A brief summary or title for the configuration (e.g., "RealPhotos Network Configuration"). + * @property description - An optional detailed description of the configuration and its purpose. + * @property networks - A record of network configurations, where keys represent the network names or IDs (e.g., "mainnet", "rinkeby"). + */ +export default type Configuration = { + version: string; + timestamp: string; + summary: string; + description?: string; + networks: Record; +}; +``` + +### Contracts + +Contracts listed **SHOULD** be limited to the subset necessary to perform specific functionality, as defined by the configuration author. + +Implementations **MUST** ensure that contract names are consistent across all networks. While the contract binary code deployed to different networks under the same name in the configuration can differ, implementations **SHOULD** ensure they represent different versions of the same contract. Differences **SHOULD** be communicated through additional custom properties or different `abiURL`. See [Extensions](#extensions). + +If a contract is not deployed on a specific network, the value `null` **MUST** explicitly indicate the absence of a contract. + + +### Extensions + +Implementations **MUST** ignore non-documented properties or support their interpretation where technically feasible. This ensures the standard remains adaptable to future use cases, enabling developers to extend configurations without breaking compatibility. +See [Example Extensions](#example-extensions). + +Non-documented properties **SHOULD** support new features or extensions, but they **MUST NOT** modify or extend the basic types of existing properties. +Union types **SHOULD NOT** be used in extensions. Their usage contradicts the [Rationale](#rationale) of the standard. +See [Incorrect Extension Example](#incorrect-extension). + +Extensions **SHOULD** use extensible data strcutures by making sure additional properties can be added at any configuration layer. + +Example of ridit and extensible data structures: + +``` typescript +# Regit data structure +type RegitConfiguration = { + documentationUrls: string[], +} + +# Extensible data structure +type ExtensibleConfiguration = { + documentation: {url: string}[], +} +``` + +## Rationale + +The design of this standard leverages the following key goals: + +### 1. Availability and Distribution + +Use JSON as a universally supported data interchange format, with libraries available in virtually all modern programming languages and platforms. This ensures: + +- **Cross-Platform Compatibility**: Developers can easily parse and generate JSON regardless of their tech stack. +- **Ease of Integration**: Existing tools and frameworks already support JSON natively, reducing implementation overhead. +- **Built-in Data Types**: JSON provides sufficient data types to describe all necessary configurations semantically, eliminating the need for manual typecasting. +- **Multinetwork Support**: The configuration supports multiple networks, ensuring projects deployed to multiple networks can propagate their configurations faster and more consistently. + +### 2. Readability by an Engineer + +The human-readable structure makes it ideal for configuration files: + +- **Clarity**: JSON's key-value pair format is intuitive and easy to understand for engineers. +- **Debugging**: Developers can quickly inspect and troubleshoot configurations directly in text editors or IDEs. +- **Simplicity**: Its simplicity ensures that even less experienced developers can comprehend and modify configurations without extensive training. +- **Human Names**: Fields like `name` and `description` provide context and improve usability for engineers working with the configuration. + +### 3. Openness for Extensions + +This standard is designed to support extensions by using JSON objects rather than rigid structures like basic types or arrays. + +- **Forward Compatibility**: Unrecognized properties can be safely ignored or leveraged without breaking existing implementations. +- **Customizability**: Developers can add additional fields to accommodate specific requirements, such as API keys or advanced metadata. +- **Scalability**: Future updates to the standard can include new attributes while maintaining backwards compatibility. + +See [Extensions](#extensions). + + +### 4. Use of Chain ID as a Unique Network Identifier + +This standard uses the **chain ID** as the unique identifier for networks rather than custom names because: + +- **Standardization**: Chain IDs are part of the Ethereum specification and are globally recognized, ensuring consistency. +- **No Collisions**: Unlike custom names, chain IDs are unique, preventing conflicts between networks. +- **Interoperability**: Using chain IDs aligns with existing Ethereum tooling and standards, making integrations seamless across diverse environments. + +## Backwards Compatibility + +This standard emphasizes extensibility while maintaining backward compatibility where feasible. It ensures adaptability to the evolving requirements of Ethereum network configurations by prioritizing flexibility in its structure. + + +### 1. Compatibility with EIP-3085 + +This standard aligns with [EIP-3085](./eip-3085.md), the Ethereum standard for wallet Add Ethereum Chain requests, wherever applicable. However, it diverges in scenarios where EIP-3085's rigid structure limits future enhancements. + +By prioritizing extensibility over strict adherence to EIP-3085, this standard ensures compatibility with existing tools while enabling future-proof enhancements to network configurations. + +## Reference Implementation + +### Example Configuration + +Below is an example configuration for an [ERC-721](./eip-721.md) project deployed to Ethereum Mainnet and Sepolia. This illustrates how the standard can represent multi-network setups. + +```json +{ + "version": "0.0.1", + "timestamp": "2025-01-01T12:22:46.471Z", + "summary": "NFT Artwork", + "description": "Artwork published by independent artist. Carefully crafted with style in one of the creative studios of the world." + "abiRoot": "https://nft-artwork.example.com/developer/abi" + "networks": { + "1": { + "name": "Ethereum Mainnet", + "testnet": false, + "nativeCurrency": { + "name": "Ether", + "symbol": "ETH", + "decimals": 18 + }, + "rpcs": { + "main": { + "url": "https://eth-rpc.example.com" + }, + "backup": { + "url": "https://eth-rpc.backup.example.com" + } + }, + "relations": { + "mainnetChainId": null, + "parentChainId": null + }, + "explorers": { + "megascan": { + "root": "https://example.org", + "block": "/block/:number", + "address": "/address/:address", + "tx": "/tx/:tx", + "nft": "/nft/:address/:token" + }, + "marketplace": { + "root": "https://nft.marketplace/networks/eth", + "block": null, + "address": "/:address", + "tx": null, + "nft": "/:address/:token" + } + }, + "contracts": { + "Registry": { + "address": "0x57928ff7b0BBc3Ee4D84481e320DdB8B941f986A", + "blockCreated": 1234567, + "abiUrl": "./Registry.sol/Registry.json" + }, + "OwnerWallet": { + "address": "0xC12237E57B088e9191BD8054Df4f5B772646a4B6", + "blockCreated": 1 + } + } + }, + "11155111": { + "name": "Sepolia", + "testnet": true, + "nativeCurrency": { + "name": "Sepolia Ether", + "symbol": "ETH", + "decimals": 18 + }, + "rpcs": { + "main": { + "url": "https://sepolia-rpc.example.com" + }, + "backup": { + "url": "https://sepolia-rpc.backup.example.com" + } + }, + "relations": { + "mainnetChainId": 1, + "parentChainId": null + }, + "explorers": { + "megascan": { + "root": "https://sepolia.example.org", + "block": "/block/:number", + "address": "/address/:address", + "tx": "/tx/:tx", + "nft": "/nft/:address/:token" + }, + "marketplace": { + "root": "https://testnets.nft.marketplace/networks/sepolia", + "block": null, + "address": "/:address", + "tx": null, + "nft": "/:address/:token" + } + }, + "contracts": { + "Registry": { + "address": "0xE13471e6E5d11205AF290261f42108f89dCae72E", + "blockCreated": 183882, + "abiUrl": "./Registry.sol/Registry.json" + }, + "OwnerWallet": { + "address": "0xC12237E57B088e9191BD8054Df4f5B772646a4B6", + "blockCreated": 1 + } + } + } + } +} +``` + +*Note*: It is **RECOMMENDED** to use a two-space indentation format for better readability and debugging. + +### Configuration Loader in Swift + +Here is an example implementation of a configuration loader in Swift Programming Language, demonstrating how to load and parse a configuration file compliant with this standard: + +``` swift +import Foundation + +struct BlockchainConfig: Codable, LoggerProvider { + struct NetworkConfig: Codable { + struct Currency: Codable { + let name: String + let symbol: String + let decimals: Int + } + + struct RPC: Codable { + let url: String + } + + struct Relations: Codable { + let mainnetChainId: Int? + let parentChainId: Int? + } + + struct Explorer: Codable { + let root: String + let block: String? + let address: String? + let tx: String? + let nft: String? + } + + struct Contract: Codable { + let address: String + let blockCreated: Int + } + + let name: String + let testnet: Bool + let nativeCurrency: Currency + let rpcs: [String: RPC] + let relations: Relations + let verify: String + let explorers: [String: Explorer?] + let contracts: [String: Contract?] + + var registryAddress: String { + contracts["Registry"]!.address + } + + func blockExplorerUrl(address: String) -> URL { + let fullPath = explorers["blockscan"]!.address!.replacingOccurrences(of: ":address", with: address) + return URL(string: explorer.root + fullPath)! + } + + func blockExplorerUrl(tx: String) -> URL { + let fullPath = explorers["blockscan"]!.tx!.replacingOccurrences(of: ":tx", with: tx) + return URL(string: explorer.root + fullPath)! + } + + func blockExplorerUrl(tokenId: Data) -> URL { + let fullPath = explorers["nftmarketplace"]!.nft! + .replacingOccurrences(of: ":address", with: contractAddress) + .replacingOccurrences(of: ":token", with: tokenId.decString) + return URL(string: explorer.root + fullPath)! + } + } + + let version: String + let timestamp: String + let description: String + let networks: [String: NetworkConfig] + + static func load() -> BlockchainConfig { + guard let url = Bundle.main.url(forResource: "config", withExtension: "json") else { + fatalError("Blockchain config not found.") + } + + do { + let data = try Data(contentsOf: url) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return try decoder.decode(BlockchainConfig.self, from: data) + } catch { + fatalError("JSON Config parse error: \(error)") + } + } +} +``` + +This Swift code provides a structured way to handle the configuration JSON and includes utility methods for accessing contract-related information. + +### Example Extensions + +#### Exposing API key + +Example of an extension that **MUST** be supported: adding an API token alongside the RPC URL when it cannot be embedded directly into the URL +(e.g., passed via an `Authorization: Bearer ` header). + + +``` typescript +type ExtendedRpcConfig = RpcConfig & { + // Bearer token for API authentication + apiKey: string, + // Maximum requests allowed per hour + hourlyThroughput?: number, + // API key restricted by contract address + contractRestrictionEnabled?: boolean, +}; + +const rpcs: Record = { + "restrictedNode": { + url: "https://example.com/eth/rpc", + apiKey: "ecc2fc8b494b60bcc9faa90d750183f2", + hourlyThroughput: 40, + contractRestrictionEnabled: true, + }, +}; +``` + +Ensure [Security Considerations](#security-considerations) are followed to protect your API key. + +#### Extending contract properties + +Example of an extension that **MUST** be supported: incorporating additional attributes to support [ERC-1967](./eip-1967.md) transparent proxies and [ERC-165](./eip-165.md) interface identification. + +``` typescript +type ExtendedContractConfig = ContractConfig & { + implementation?: string; + proxyAdmin?: string; + supportsInterface: string[]; +}; + +const contracts: Record = { + Registry: { + "address": "0x57928ff7b0BBc3Ee4D84481e320DdB8B941f986A", + "blockCreated": 123456, + "implementation": "0x489B4BC8bCA12cCeafb84aa78918b8E28594C391", + "proxyAdmin": "0xFd72EeDa802481027e4200E61A5dF052287eec27", + // ERC-721 & ERC-165 interfaces + "supportsInterface": ["0x01ffc9a7", "0x80ac58cd"] + } +}; +``` + +#### Incorrect extension + +Example of an incorrect extension that **MUST NOT** be supported: representing `blockCreated` as a hexadecimal string instead of an integer. + +``` typescript +const contracts = { + Registry: { + "address": "0x57928ff7b0BBc3Ee4D84481e320DdB8B941f986A", + "blockCreated": "0x2af821", + } +}; +``` + +It causes existing implementation to fail parsing `blockCreated` property expecting it to be a `number`, not a `string`. + +## Security Considerations + +The network configuration defined by this standard is intended to include only public information that is already accessible through tools like block explorers. By structuring this information in a standardized format, the configuration becomes more convenient and efficient for developers to use while maintaining the integrity of public data. + +To ensure security and privacy, the following considerations **MUST** be adhered to: + +### 1. Public Information Only + +- The configuration **SHOULD** include only publicly available details. +- All information included in the configuration should already be discoverable through public tools or services, such as block explorers. +- Including private or sensitive information **MUST** be ensured with additional layer of protection. + +### 2. Exposing API Keys + +Exposing API keys must be handled with caution and accompanied by a thorough evaluation of associated risks. When including API keys, the following restrictions and best practices **SHOULD** be implemented: + +- **Scope Restrictions**: + - API keys **SHOULD** be limited in scope to specific actions or contract addresses. + - Consider implementing IP restrictions where possible. + +- **Rate Limits**: + - Include throughput limits (e.g., hourly or daily request caps) to mitigate abuse. + +- **Usage**: + - Exposing API keys **MUST NOT** be done in publicly distributed configurations. + - Use environment variables or other secure mechanisms to manage API keys internally within an organization. + +### 3. Exclusion of Sensitive Information + +The configuration **MUST NOT** include the following sensitive data: + +- Private keys +- Wallet recovery phrases or mnemonics +- Any information that could compromise the security or privacy of users or systems. + +### 4. Low Overall Security Risk + +The overall security risks of using this standard are minimal due to its compatibility with established Ethereum ecosystem security practices, including: + +- Verification of public data through block explorers. +- Adherence to existing security standards like JSON schema validation and ABI checksum verification. + +By aligning with proven methodologies and widely accepted protocols, the standard minimizes potential vulnerabilities while ensuring robust functionality. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md).