diff --git a/ERCS/erc-8048.md b/ERCS/erc-8048.md new file mode 100644 index 00000000000..5d3220c62b5 --- /dev/null +++ b/ERCS/erc-8048.md @@ -0,0 +1,176 @@ +--- +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) +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-9-30 +--- + +## Abstract + +This ERC defines an onchain metadata standard for multi-token and NFT registries including ERC-721, ERC-1155, ERC-6909, and ERC-8004. 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, ERC-6909, and ERC-8004 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, ERC-6909, and ERC-8004 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, ERC-6909, or ERC-8004 compliant registries. + +### Required Metadata Function and Event + +Contracts implementing this ERC MUST implement the following interface: + +```solidity +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); +} +``` + +- `getMetadata(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); +``` + +### 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 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:** + +- Key: `"agentWallet"` +- Value: `bytes("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6")` +- Key: `"agentName"` +- Value: `bytes("DeFi Trading Agent")` + +#### 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))` + + +## 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, ERC-6909, and ERC-8004 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 + +- Fully compatible with ERC-721, ERC-1155, ERC-6909, and ERC-8004. +- 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 + +```solidity +// SPDX-License-Identifier: MIT +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 + +```solidity +// SPDX-License-Identifier: MIT +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, ERC-6909, or ERC-8004 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](../LICENSE.md).