ERC-8167 Modular Dispatch Proxy (Modular Dispatch Proxies)
Final · Other · created 2026-02-16
A standard for proxy contracts that route function calls to different logic modules according to the first 4-byte selector. It lets tools look up a proxy's function list and the implementation address of each function.
Why it is needed
The widely used proxy contracts on Ethereum usually forward every call to a single implementation contract. This single-implementation structure easily runs into the contract code size limit, and an upgrade must swap out the entire implementation at once, which risks breaking unrelated features, and it is also hard to test and verify. A proxy that splits its functionality into multiple modules avoids these problems, but without a standard lookup method, tools such as wallets and block explorers have no way to find out which functions the proxy has and which contract implements each function. This standard unifies that lookup method as a minimal interface.
What to implement
event SelectorDelegated(bytes4 indexed selector, address indexed delegate);: An event emitted when a delegate logic module is assigned to a selector. If delegate is address(0), it means removal.function implementation(bytes4 selector) external view returns (address);: Returns the delegate address that handles the given selector. If there is no such function, it returns address(0).function selectors() external view returns (bytes4[] memory);: A function that exposes the proxy's ABI. It must return every function selector that has an implementation.error FunctionNotFound(bytes4 selector);: When no delegate is set for a selector, the proxy is recommended to revert with this error.
In depth
Dispatch Rules
A modular dispatch proxy MUST forward the entire call data through delegatecall to the delegate that corresponds to the first 4 bytes of the call data (calldata). A 4-byte selector is used because the most widely supported ABI is Solidity's 4-byte ABI, and the proxy dispatches functions by the same 4 bytes. The IERC8167 functions SHOULD be implemented in a delegate rather than in the proxy body, and the constructor SHOULD set at least one delegate.
Design of implementation(bytes4)
The implementation address can also be found with eth_getStorageAt, but a common interface lets it be looked up the same way regardless of the storage layout or implementation approach used. The function name matches the convention of existing single-implementation proxies, with a selector parameter added. If the function is not found, it returns address(0).
Reconstructing the ABI with selectors()
A tool builds the proxy's ABI in three steps. First, it looks up implementation(selector) for each selector from selectors(). Second, for each unique implementation, it checks whether the code is verified, fetching the ABI if it is and letting the user supply the ABI otherwise. Third, it matches the selectors against the implementations' ABIs to identify the functions the proxy supports. Selectors can also be obtained from the SelectorDelegated event, but the selectors() function provides the same information without log access. Log queries can be slow without a database index. A compressed encoding could reduce memory allocation, but a bytes4 array is the simplest for tools to interpret.
Upgrades and Storage Layout Are Out of Scope
This standard does not specify an upgrade function. A versioning scheme for atomic batch upgrades can be defined by another standard that extends this one. It does not specify a storage layout either, and patterns that prevent mistakes such as storage collisions can likewise be proposed by another standard. The reference implementation includes three files: an interface, a namespace-based storage layout, and a proxy implementation with delegates for inspection and administration.
Relationship to ERC-2535 (Diamonds)
This standard improves on ERC-2535 in three ways. It drops the diamond terminology. It has fewer and simpler lookup functions, and its upgrade event is also simpler. An existing modular proxy can migrate to this standard by adding the lookup functions and optionally emitting the SelectorDelegated event for every installed function.
Migration Path for Single-Implementation Proxies
An existing upgradeable single-implementation proxy migrates in three steps. First, upgrade to an implementation that has a method for populating the selector-to-delegate mapping. Second, populate the mapping for every method in the ABI and for the lookup functions. Third, set a dispatch proxy that uses the populated mapping as the implementation.
Things to watch out for
- Access Control for Upgrade Functions: An upgrade function must have access control in some form. The design of the access control itself is outside the scope of this standard.
- No Self-Destruct in Delegates: A delegate must not self-destruct. If a delegate can destroy itself, the proxies that use it can break.
- Beware of Storage Layout Collisions: A proxy upgrade must take care not to shift storage indexes. If they shift, contract data is corrupted. Two methods are known for preventing storage overlap between delegates. One is to define a single shared storage layout in a common parent class that all delegates inherit, with subclasses declaring no additional storage. The other is to use storage namespaces such as ERC-7201 or ERC-8042. The latter suits shared libraries.