Contract-Level Onchain Metadata
Abstract
This ERC lets a contract store metadata about itself onchain as key-value pairs, with arbitrary bytes as values. Every update emits an event, and clients read the records directly from the contract.
Motivation
Contract metadata today typically relies on offchain storage such as URLs or IPFS, creating trust and availability risks—servers go down, domains expire, and malicious actors can modify data without onchain record. Storing metadata onchain makes contract identity censorship-resistant and enables wallets and block explorers to display verifiable information without trusting external services.
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.
Required Metadata Function and Event
Contracts implementing this ERC MUST implement the following interface:
interface IERC8049 {
/// @notice Get contract metadata value for a key.
function contractMetadata(string calldata key) external view returns (bytes memory);
/// @notice Emitted when contract metadata is updated.
event ContractMetadataUpdated(string indexed indexedKey, string key, bytes value);
}
Contracts implementing this ERC MAY also expose a setContractMetadata(string calldata key, bytes calldata value) function to allow metadata updates, with write policy determined by the contract.
Contracts implementing this ERC MUST emit the following event when metadata is set:
event ContractMetadataUpdated(string indexed indexedKey, string key, bytes value);
Interface ID
The interface ID is 0x8ec3f882.
Key/Value Pairs
This ERC specifies that the key is a string type and the value is bytes type. This provides flexibility for storing any type of data while maintaining an intuitive string-based key interface.
Optional Key Parameters
Keys MAY include parameters to represent variations or instances of a metadata type, such as "registration: 1" or "name: Maria"; see ERC-8119: Key Parameters.
Optional Diamond Storage
Contracts implementing this ERC MAY use Diamond Storage pattern for predictable storage locations. If implemented, contracts MUST use the namespace ID "erc8049.contract.metadata.storage".
The Diamond Storage pattern provides predictable storage locations for data, which is useful for cross-chain applications using inclusion proofs and for upgradable contracts. For more details on Diamond Storage, see ERC-8042.
Value Interpretation
If no standard is specified for a metadata value, clients MAY assume the value is a UTF-8 encoded string (bytes(string)) unless otherwise specified by the implementing contract or protocol.
Examples
Example: Basic Contract Information
A contract can store basic information about itself:
- Key:
"name"→ Value:bytes("MyToken") - Key:
"description"→ Value:bytes("A decentralized exchange") - Key:
"collaborators"→ Value:bytes(abi.encodePacked(address1, address2, address3))
Example: ENS Name for Contract
A contract can specify its ENS name using this standard:
- Key:
"ens_name"→ Value:bytes("mycontract.eth")
This allows clients to discover the contract’s ENS name and resolve it to get additional information about the contract.
AI Agent Metadata Profile
This profile defines a reserved set of this ERC’s contract-level metadata keys that associate an ERC-20 token contract with a single AI agent, and is nicknamed ERC-20Agent. It is a standard use of this ERC’s existing key-value interface; it does not define a new Solidity interface, a new ERC-165 interface ID, new functions, or new events. Consumers read and write these records through contractMetadata(string) and rely on the ContractMetadataUpdated event for change notifications, exactly as for any other key under this ERC.
Because this ERC’s records are contract-level, that agent belongs to the token contract as a whole, not to any holder or balance. What that agent is used for is left to the issuer. It might provide information about the token through agent-user or agent-UI chat, represent token-based governance, or serve any other mechanism the issuer intends.
A contract implementing this profile MUST implement ERC-20 and this ERC. The profile reserves the following keys:
| Key | Meaning | Value (bytes) |
|---|---|---|
context |
Agent context for the contract. | UTF-8 text. Markdown is RECOMMENDED; issuers MAY embed a fenced JSON block for machine-readable fields. |
endpoint[<type>] |
Endpoint URI for the named protocol <type>. |
UTF-8 URI. For endpoint[web], an https URI is RECOMMENDED. |
address[<chain-id>] |
The agent’s account on the chain identified by <chain-id>, where <chain-id> is the ERC-7930 Chain Identifier (see “Chain-keyed addresses” below). |
The 20-byte EVM address (raw, not text). |
Endpoint types
Keys are case-sensitive (the value of key is compared as exact bytes). Endpoint types in this profile are therefore lowercase: endpoint[mcp] and endpoint[MCP] are distinct keys, and only the lowercase spelling carries the meaning reserved here. Implementations MUST write the canonical lowercase spelling for the types this profile reserves.
The canonical endpoint types are:
mcp: Model Context Protocol endpoint.a2a: Agent-to-Agent endpoint.web: general web endpoint. The value MAY be any URI, withhttpsRECOMMENDED.x402: payment-enabled endpoint.
Additional endpoint types MAY be used without changing this profile, provided they use the endpoint[<type>] form. Standards that reserve new types SHOULD define their exact canonical (lowercase) spelling.
Chain-keyed addresses
In address[<chain-id>], <chain-id> is the ERC-7930 Chain Identifier, an Interoperable Address with a zero-length address part, encoded as a lowercase 0x-prefixed hex string. The stored value is a normal 20-byte EVM address, not an ERC-7930 interoperable address; ERC-7930 is used only to name the chain.
For example, the Chain Identifier for Ethereum mainnet (chain ID 1) is 0x00010000010100. The agent’s mainnet account would be stored as:
- Key:
"address[0x00010000010100]"→ Value: the 20 bytes of the EVM address.
Client expectations
Clients SHOULD treat both context and endpoints as untrusted input: they are issuer-supplied data, and reading them implies no verification of the servers, keys, balances, or memory they reference.
Optional Metadata Hooks
Contracts implementing this ERC MAY use hooks to redirect metadata resolution to a different contract. For the full specification, see ERC-8121. When using hooks with contract metadata, the target function MUST be contractMetadata(string) returning bytes. The hook selector is 0x9e574b14.
Rationale
This design prioritizes simplicity and flexibility by using a string-key, bytes-value store that provides an intuitive interface for any type of contract metadata. The minimal interface with a single contractMetadata function provides all necessary functionality. The optional setContractMetadata function enables flexible access control for metadata updates. The required ContractMetadataUpdated event provides transparent audit trails with indexed key for efficient filtering. Contracts that need predictable storage locations can optionally use Diamond Storage pattern. This makes the standard suitable for diverse use cases including contract identification, collaboration tracking, and custom metadata storage.
Backwards Compatibility
- Fully compatible with existing smart contracts.
- Non-supporting clients can ignore the scheme.
Reference Implementation
The interface is defined in the Required Metadata Function and Event section above. Here are reference implementations:
Basic Implementation
pragma solidity ^0.8.25;
import "./IERC8049.sol";
contract BasicContractMetadata is IERC8049 {
// Simple mapping for contract-level metadata
mapping(string key => bytes value) private _metadata;
function contractMetadata(string calldata key) external view override returns (bytes memory) {
return _metadata[key];
}
function setContractMetadata(string calldata key, bytes calldata value) external {
_metadata[key] = value;
emit ContractMetadataUpdated(key, key, value);
}
}
Diamond Storage Implementation
pragma solidity ^0.8.25;
import "./IERC8049.sol";
contract DiamondContractMetadata is IERC8049 {
struct ContractMetadataStorage {
mapping(string key => bytes value) metadata;
}
// keccak256("erc8049.contract.metadata.storage")
bytes32 private constant CONTRACT_METADATA_STORAGE_LOCATION =
keccak256("erc8049.contract.metadata.storage");
function _contractMetadataStorage() private pure returns (ContractMetadataStorage storage $) {
bytes32 location = CONTRACT_METADATA_STORAGE_LOCATION;
assembly {
$.slot := location
}
}
function contractMetadata(string calldata key) external view override returns (bytes memory) {
ContractMetadataStorage storage $ = _contractMetadataStorage();
return $.metadata[key];
}
function setContractMetadata(string calldata key, bytes calldata value) external {
ContractMetadataStorage storage $ = _contractMetadataStorage();
$.metadata[key] = value;
emit ContractMetadataUpdated(key, key, value);
}
}
Security Considerations
This ERC is designed to put metadata onchain, providing security benefits through onchain storage.
Implementations that choose to use the optional Diamond Storage pattern should consider the security considerations of ERC-8042.
Copyright
Copyright and related rights waived via CC0.