ERC-8048 - Onchain Metadata for Token Registries

Created 2025-09-30
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

This ERC defines an onchain metadata standard for multi-token and NFT registries including ERC-721, ERC-1155, and ERC-6909. The standard provides a key-value store allowing for arbitrary bytes to be stored onchain.

Motivation

This ERC addresses the need for fully onchain metadata while maintaining compatibility with existing ERC-721, ERC-1155, and ERC-6909 standards. It has been a long-felt need for developers to store metadata onchain for NFTs and other multitoken contracts; however, there has been no uniform standard way to do this. Some projects have used the tokenURI field to store metadata onchain using Data URLs, which introduces gas inefficiencies and has other downstream effects (for example making storage proofs more complex). This standard provides a uniform way to store metadata onchain, and is backwards compatible with existing ERC-721, ERC-1155, and ERC-6909 standards.

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.

Scope

This ERC is an optional extension that MAY be implemented by any ERC-721, ERC-1155, or ERC-6909 compliant registries.

Required Metadata Function and Event

Contracts implementing this ERC MUST implement the following interface:

interface IOnchainMetadata {
    /// @notice Get metadata value for a key.
    function getMetadata(uint256 tokenId, string calldata key) external view returns (bytes memory);

    /// @notice Emitted when metadata is set for a token.
    event MetadataSet(uint256 indexed tokenId, string indexed indexedKey, string key, bytes value);
}

Contracts implementing this ERC MAY also expose a setMetadata(uint256 tokenId, 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 MetadataSet(uint256 indexed tokenId, string indexed indexedKey, string key, bytes value);

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 for the standard parameterized key format.

Optional Diamond Storage

Contracts implementing this ERC MAY use Diamond Storage pattern for predictable storage locations. If implemented, contracts MUST use the namespace ID "erc8048.onchain.metadata.storage".

The Diamond Storage pattern provides predictable storage locations for data, which is useful for cross-chain applications using inclusion proofs. For more details on Diamond Storage, see ERC-8042.

Examples

The inspiration for this standard was trustless AI agents. The registry extends ERC-721 by adding getMetadata and setMetadata functions for optional extra on-chain agent metadata.

Example: Context for LLM-Facing Agent Metadata

Examples of keys are "agentWallet" or "agentName".

Example:

Example: Biometric Identity for Proof of Personhood

A biometric identity system using open source hardware to create universal proof of personhood tokens.

Optional Metadata Hooks

Contracts implementing this ERC MAY use metadata hooks to redirect record resolution to a different contract for secure resolution from known contracts, such as singleton registries with verifiable security properties.

For the full specification of metadata hooks, see ERC-8121 (Metadata Hooks). Hooks are encoded in the metadata value itself and allow clients to jump to another contract to resolve the metadata value. When using hooks for token metadata with this ERC, the return type MUST be bytes and the hook encoding MUST be bytes.

Rationale

This design prioritizes simplicity and flexibility by using a string-key, bytes-value store that provides an intuitive interface for any type of metadata. The minimal interface with a single getMetadata function provides all necessary functionality while remaining backwards compatible with existing ERC-721, ERC-1155, and ERC-6909 standards. The optional setMetadata function enables flexible access control for metadata updates. The required MetadataSet event provides transparent audit trails with indexed tokenId and indexedKey for efficient filtering. This makes the standard suitable for diverse use cases including AI agents, proof of personhood systems, and custom metadata storage.

Backwards Compatibility

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 "./IOnchainMetadata.sol";

contract OnchainMetadataExample is IOnchainMetadata {
    // Mapping from tokenId => key => value
    mapping(uint256 => mapping(string => bytes)) private _metadata;

    /// @notice Get metadata value for a key
    function getMetadata(uint256 tokenId, string calldata key) 
        external view override returns (bytes memory) {
        return _metadata[tokenId][key];
    }

    /// @notice Set metadata for a token (optional implementation)
    function setMetadata(uint256 tokenId, string calldata key, bytes calldata value) 
        external {
        _metadata[tokenId][key] = value;
        emit MetadataSet(tokenId, key, key, value);
    }
}

Diamond Storage Implementation

pragma solidity ^0.8.20;

import "./IOnchainMetadata.sol";

contract OnchainMetadataDiamondExample is IOnchainMetadata {
    struct OnchainMetadataStorage {
        mapping(uint256 tokenId => mapping(string key => bytes value)) metadata;
    }

    // keccak256("erc8048.onchain.metadata.storage")
    bytes32 private constant ONCHAIN_METADATA_STORAGE_LOCATION =
        keccak256("erc8048.onchain.metadata.storage");

    function _getOnchainMetadataStorage() private pure returns (OnchainMetadataStorage storage $) {
        bytes32 location = ONCHAIN_METADATA_STORAGE_LOCATION;
        assembly {
            $.slot := location
        }
    }

    function getMetadata(uint256 tokenId, string calldata key) 
        external view override returns (bytes memory) {
        OnchainMetadataStorage storage $ = _getOnchainMetadataStorage();
        return $.metadata[tokenId][key];
    }

    function setMetadata(uint256 tokenId, string calldata key, bytes calldata value) 
        external {
        OnchainMetadataStorage storage $ = _getOnchainMetadataStorage();
        $.metadata[tokenId][key] = value;
        emit MetadataSet(tokenId, key, key, value);
    }
}

Implementations should follow the standard ERC-721, ERC-1155, or ERC-6909 patterns while adding the required metadata function and event.

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.