ERC-XXXX: Contract Version Interface — Standardizing version() Across Smart Contracts

Summary

I’d like to propose a minimal ERC that standardizes a version() view function for smart contracts : a simple, on-chain way for integrators, auditors, and tooling to identify which implementation version is currently deployed.


Motivation

Almost every non-trivial protocol exposes some form of version string today but each does it differently, and most don’t signal interface support via ERC-165. This creates friction:

  • Integrators need custom adapters per protocol to detect version.
  • Incident responders can’t quickly confirm which deployment they’re looking at.
  • Security auditors have no standard way to map a live contract to an audited codebase.
  • Governance and upgrade tooling must rely on off-chain bookkeeping rather than on-chain introspection.

ERC-3643 (permissioned tokens) already mandates a version() function and it has proven useful in practice. The goal of this ERC is to extract that pattern, make it token-agnostic, and give it proper ERC-165 support so it can be used across DeFi, infrastructure contracts, and governance systems.


Specification (summary)

interface IERCVersion {
    /// @notice Returns the implementation version string.
    /// @return The version value (for example "1.0.0").
    function version() external view returns (string memory);
}

Required behavior:

  • version() MUST be a view function and MUST NOT revert under normal operation.
  • version() MUST return a non-empty string.
  • Implementations SHOULD use SemVer-style formatting: MAJOR.MINOR.PATCH (e.g. 1.0.0, 3.2.1).
  • Implementations SHOULD support ERC-165. If ERC-165 is supported, supportsInterface(0x54fd4d50) MUST return true.

Deployment model: compatible with both immutable contracts and proxy-based upgradeable systems. In upgradeable deployments, version() SHOULD reflect the active implementation seen by users.

Backwards compatibility with ERC-3643: integrators MAY treat existing ERC-3643 contracts exposing a compatible version() as implementing this interface, even without ERC-165 support.


Design choices & rationale

Why a single function? Minimal surface maximizes adoption. Every additional requirement is a reason not to implement the interface.

Why string and not bytes32? Human readability in explorers and tooling was prioritized over marginal gas savings. A version string is not on a hot path.

Why recommend ERC-165 but not require it? Requiring ERC-165 raises the adoption barrier for contracts that don’t implement interface discovery. Making it a SHOULD keeps the interface minimal. When ERC-165 is present, advertising this interface is mandatory , so integrators get consistent detection when the signal exists, without excluding contracts that opt out.

Why SemVer and not an integer? SemVer is already the de facto standard in the ecosystem (npm, cargo, go modules). A MAJOR.MINOR.PATCH string is immediately meaningful to developers and tools without additional documentation.


What this ERC does NOT define

  • Version format enforcement on-chain: the contract cannot validate its own version string. Format compliance is a social/tooling convention.
  • Version change events: we deliberately excluded an event for version changes to keep the interface minimal. Governance events in upgradeability frameworks already cover this.
  • Semantic meaning of version increments: implementors define their own versioning policy and SHOULD document it publicly.

Open questions for the community

  1. Should the ERC mandate a specific version format (SemVer regex) or keep it a SHOULD? A stricter requirement would improve machine-comparability but reduce flexibility.
  2. Should there be a versionChanged event for upgradeable systems, even if optional?

Prior art & references

  • ERC-3643: mandates a version() function on permissioned token contracts; direct inspiration for this ERC.
  • ERC-165: interface detection standard, recommended by this ERC.
  • SemVer 2.0.0: recommended versioning format.

Looking forward to feedback!