--- eip: 8048 title: Onchain Metadata for Token Registries description: A key-value store interface that allows registries to store and retrieve arbitrary bytes as metadata directly onchain. author: Prem Makeig (@nxt3d), Rafael Abuawad (@rafael-abuawad) discussions-to: https://ethereum-magicians.org/t/erc-8048-onchain-metadata-for-multi-token-and-nft-registries/25820 status: Draft type: Standards Track category: ERC created: 2025-09-30 requires: 165 --- ## Abstract This ERC defines an onchain metadata standard for multi-token and NFT registries including [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md), and [ERC-6909](./eip-6909.md). 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](./eip-721.md), [ERC-1155](./eip-1155.md), and [ERC-6909](./eip-6909.md) 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](./eip-721.md), [ERC-1155](./eip-1155.md), and [ERC-6909](./eip-6909.md) 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](./eip-721.md), [ERC-1155](./eip-1155.md), or [ERC-6909](./eip-6909.md) compliant registries. ### Required Metadata Function and Event Contracts implementing this ERC MUST implement the following interface: ```solidity interface IERC8048Metadata { /// @notice Get metadata value for a key. function metadata(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); } ``` - `metadata(tokenId, key)`: Returns the metadata value for the given token ID and key as bytes 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: ```solidity event MetadataSet(uint256 indexed tokenId, string indexed indexedKey, string key, bytes value); ``` ### Interface ID The interface ID is `0xdf670be1`. ### 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](./eip-8119.md) 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](./eip-8042.md). ### Examples It is possible to use this standard to tokenize an agent as an NFT: each token ID is the agent, and `metadata` holds agent fields as UTF-8 in `bytes`. #### Example: AI agent metadata (NFT as agent) Two key patterns: - **`context`** — one key. UTF-8 Markdown is enough for humans and models; issuers MAY also embed a JSON code block so clients can parse token lists, policy URIs, or version fields without a second key. - **`endpoint[]`** — one URL per protocol; `` is lowercase (`mcp`, `a2a`, `ag-ui`, …). Example for `tokenId == 1`: store the UTF-8 encoding of a Markdown document like the following as the `bytes` value for `"context"` (shown as a file; not a Solidity literal): ~~~~markdown I am an agent that can swap tokens on Ethereum mainnet. I maintain an official token list and only suggest swaps where both assets appear on that list. ```json { "tokenListUri": "ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/tokenlist.json", "network": "mainnet", "defaultSlippageBps": 50 } ``` ~~~~ Endpoint keys for the same token: - `"endpoint[mcp]"` → `bytes("https://agents.example.com/mcp/1")` - `"endpoint[a2a]"` → `bytes("https://agents.example.com/a2a/1")` - `"endpoint[ag-ui]"` → `bytes("https://agents.example.com/ui/1")` #### Example: Biometric Identity for Proof of Personhood A biometric identity system using open source hardware to create universal proof of personhood tokens. - Key: `"biometric_hash"` → Value: `bytes(bytes32(identity_commitment))` - Key: `"verification_time"` → Value: `bytes(bytes32(timestamp))` - Key: `"device_proof"` → Value: `bytes(bytes32(device_attestation))` ### 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](./eip-8121.md) (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`. ### Onchain Metadata Contract Reference (Optional Extension) Many [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md), and [ERC-6909](./eip-6909.md) registries are already deployed without the `metadata` function or without storage reserved for the onchain key-value mapping. Adding that interface or storage layout is often impossible without an upgrade path, so those contracts cannot adopt the core pattern of this ERC on the token contract itself. This optional extension keeps discovery compatible with existing `tokenURI` / `uri` flows while directing clients to a separate **metadata contract** that implements `metadata(uint256,string)` as defined in this ERC. The target is identified by an [ERC-7930](./eip-7930.md) _Interoperable Address_ carried in JSON. The function to call and its signature are fixed by this specification. #### `tokenURI` / `uri` JSON field When `tokenURI` ([ERC-721](./eip-721.md)), `uri` ([ERC-1155](./eip-1155.md)), or the equivalent URI function for [ERC-6909](./eip-6909.md) returns a string that resolves to a JSON document (including JSON embedded in a `data:` URL), the document MAY include a top-level string property named `"metadata_contract"`. The value of `"metadata_contract"` MUST be the interoperable address encoded as a single JSON string: a `0x` prefix followed by the hex encoding of the [ERC-7930](./eip-7930.md) bytes, all lowercase. Agent NFT Example: ```json { "name": "Example", "image": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", "metadata_contract": "0x00010000010114d8da6bf26964af9d7eed9e03e53415d37aa96045", "endpoint[mcp]": "https://agents.example.com/mcp/1", "context": "I am an agent that can swap tokens on Ethereum mainnet." } ``` It is possible to include metadata both directly in the `tokenURI` / `uri` JSON and in the `metadata_contract` for additional metadata. #### Client behavior Clients that support this extension: 1. Obtain and parse the JSON from `tokenURI` / `uri` as they already do for display metadata. 2. If a top-level `"metadata_contract"` string is present, decode the value as [ERC-7930](./eip-7930.md) interoperable address bytes from the `0x`-prefixed hex string. 3. Parse the interoperable address to obtain the target chain and contract address. 4. Read `metadata(uint256 tokenId, string key)` from that contract on the target chain, using the same `tokenId` as for the `tokenURI` / `uri` call and the metadata key from this ERC. The return value is the same as if the token contract had stored the mapping locally. In most cases the metadata contract SHOULD be on the same chain as the NFT token registry, but it is possible to use a metadata contract on a different chain, depending on support for cross-chain reads in clients and in the ecosystem in general. Clients that do not implement this extension or do not trust the target contract address MAY ignore `"metadata_contract"` and continue to use other JSON fields. #### Relationship to `IERC8048Metadata` Registries using only this extension are NOT required to implement `IERC8048Metadata` on the token contract. The contract referenced by `metadata_contract` MUST implement the `metadata(uint256,string) external view returns (bytes)` function from this ERC (or a compatible implementation). Security: clients SHOULD only call metadata contracts they consider trustworthy; a malicious or mistaken `metadata_contract` could return attacker-controlled `bytes`. Clients SHOULD show or verify the target address the same as for any new contract interaction. ## Rationale This ERC standardizes a simple string-key, bytes-value metadata store for existing token registries. The optional `setMetadata` function allows updates under the contract’s chosen write policy, and `MetadataSet` provides an onchain audit trail. **Onchain Metadata Contract Reference** extends this model to already-deployed tokens by pointing `metadata_contract` to a sidecar via a [ERC-7930](./eip-7930.md) address in `tokenURI` / `uri` JSON. ## Backwards Compatibility - Fully compatible with [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md), and [ERC-6909](./eip-6909.md). - Non-supporting clients can ignore the scheme. - The **Onchain Metadata Contract Reference** extension only adds an optional JSON property; clients that do not read `"metadata_contract"` behave as they do today. ## Reference Implementation The interface is defined in the Required Metadata Function and Event section above. Here are reference implementations: ### Basic Implementation ```solidity pragma solidity ^0.8.25; import "./IERC8048Metadata.sol"; contract OnchainMetadataExample is IERC8048Metadata { // Mapping from tokenId => key => value mapping(uint256 => mapping(string => bytes)) private _metadata; /// @notice Get metadata value for a key function metadata(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 ```solidity pragma solidity ^0.8.20; import "./IERC8048Metadata.sol"; contract OnchainMetadataDiamondExample is IERC8048Metadata { 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 metadata(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](./eip-721.md), [ERC-1155](./eip-1155.md), or [ERC-6909](./eip-6909.md) 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](./eip-8042.md). ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).