Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7508: Dynamic On-Chain Token Attributes Repository

Dynamic on-chain storage of token attributes in a public-good repository.

Authors Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer)
Created 2023-08-15
Discussion Link https://ethereum-magicians.org/t/dynamic-on-chain-token-attributes-repository/15667
Requires EIP-165

Abstract

The Public On-Chain Non-Fungible Token Attributes Repository standard provides the ability for ERC-721 and ERC-1155 compatible tokens to store their attributes on-chain available to any external smart contract interacting with them.

This proposal introduces the ability to assign attributes to NFTs in a public non-gated repository smart contract that is accessible at the same address in all of the networks. The repository smart contract is designed to be a common-good repository, meaning that it can be used by any ERC-721 or ERC-1155 compatible token.

Motivation

With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability to store token’s attributes on chain allows for greater utility of tokens as it fosters cross-collection interactivity and provides perpetual store of attributes.

This ERC introduces new utilities for ERC-721 and ERC-1155 based tokens in the following areas:

Cross-Collection Interactivity

Storing attributes on-chain in a predictable format allows for cross-collection interactivity. This means that the attributes of a token can be used by any external smart contract without the need for the token to be aware of the external smart contract.

For example, a token can represent a game character with its set of attributes and can be used in an unrelated game with the same stats without the need for retrieving these attributes from an off-chain source. This ensures that the data the game is using is legitimate and not tampered with in order to gain an advantage.

Perpetual Store of Attributes

Standardized on-chain token attributes allow for their perpetual storage.

With off-chain attributes storage, the attributes are only available as long as the off-chain storage is available. If the storage is taken down, the attributes are lost. With on-chain attributes storage, the attributes are available as long as the blockchain is available. This increases the value of the token as it ensures that the attributes are available for as long as the token exists.

Token Evolution

On-Chain storage of token attributes allows for the token to evolve over time. Owner’s actions can impact the attributes of the token. Since the attributes are stored on chain, the smart contract has the ability to modify the attribute once certain thresholds are met. This allows for token to become more interactive and reflect owner’s dedication and effort.

Dynamic State Tracking

On-Chain storage of token attributes allows for dynamic state tracking. The attributes can be used to track the state of the token and its owner. This allows for the token to be used in a variety of use cases. One such use case is supply chains; the token can represent a product and its attributes can be used to track the state of the product as it transitions from pending, shipped, delivered, etc.

Specification

Interface

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

/// @title ERC-7508 Public On-Chain NFT Attributes Repository
/// @dev See https://eips.ethereum.org/EIPS/eip-7508
/// @dev Note: the ERC-165 identifier for this interface is 0x212206a8.

pragma solidity ^0.8.21;

interface IERC7508 is IERC165 {
    /**
     * @notice A list of supported access types.
     * @return The `Owner` type, where only the owner can manage the parameter.
     * @return The `Collaborator` type, where only the collaborators can manage the parameter.
     * @return The `OwnerOrCollaborator` type, where only the owner or collaborators can manage the parameter.
     * @return The `TokenOwner` type, where only the token owner can manage the parameters of their tokens.
     * @return The `SpecificAddress` type, where only specific addresses can manage the parameter.
     */
    enum AccessType {
        Owner,
        Collaborator,
        OwnerOrCollaborator,
        TokenOwner,
        SpecificAddress
    }

    /**
     * @notice Structure used to represent an address attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct AddressAttribute {
        string key;
        address value;
    }

    /**
     * @notice Structure used to represent a boolean attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct BoolAttribute {
        string key;
        bool value;
    }

    /**
     * @notice Structure used to represent a bytes attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct BytesAttribute {
        string key;
        bytes value;
    }

    /**
     * @notice Structure used to represent an int attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct IntAttribute {
        string key;
        int256 value;
    }

    /**
     * @notice Structure used to represent a string attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct StringAttribute {
        string key;
        string value;
    }

    /**
     * @notice Structure used to represent an uint attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct UintAttribute {
        string key;
        uint256 value;
    }

    /**
     * @notice Used to notify listeners that a new collection has been registered to use the repository.
     * @param collection Address of the collection
     * @param owner Address of the owner of the collection; the addess authorized to manage the access control
     * @param registeringAddress Address that registered the collection
     * @param useOwnable A boolean value indicating whether the collection uses the Ownable extension to verify the
     *  owner (`true`) or not (`false`)
     */
    event AccessControlRegistration(
        address indexed collection,
        address indexed owner,
        address indexed registeringAddress,
        bool useOwnable
    );

    /**
     * @notice Used to notify listeners that the access control settings for a specific parameter have been updated.
     * @param collection Address of the collection
     * @param key The name of the parameter for which the access control settings have been updated
     * @param accessType The AccessType of the parameter for which the access control settings have been updated
     * @param specificAddress The specific addresses that has been updated
     */
    event AccessControlUpdate(
        address indexed collection,
        string key,
        AccessType accessType,
        address specificAddress
    );

    /**
     * @notice Used to notify listeners that the metadata URI for a collection has been updated.
     * @param collection Address of the collection
     * @param attributesMetadataURI The new attributes metadata URI
     */
    event MetadataURIUpdated(
        address indexed collection,
        string attributesMetadataURI
    );

    /**
     * @notice Used to notify listeners that a new collaborator has been added or removed.
     * @param collection Address of the collection
     * @param collaborator Address of the collaborator
     * @param isCollaborator A boolean value indicating whether the collaborator has been added (`true`) or removed
     *  (`false`)
     */
    event CollaboratorUpdate(
        address indexed collection,
        address indexed collaborator,
        bool isCollaborator
    );

    /**
     * @notice Used to notify listeners that an address attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event AddressAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        address value
    );

    /**
     * @notice Used to notify listeners that a boolean attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event BoolAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        bool value
    );

    /**
     * @notice Used to notify listeners that a bytes attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event BytesAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        bytes value
    );

    /**
     * @notice Used to notify listeners that an int attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event IntAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        int256 value
    );

    /**
     * @notice Used to notify listeners that a string attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event StringAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        string value
    );

    /**
     * @notice Used to notify listeners that an uint attribute has been updated.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @param value The new value of the attribute
     */
    event UintAttributeUpdated(
        address indexed collection,
        uint256 indexed tokenId,
        string key,
        uint256 value
    );

    // ------------------- ACCESS CONTROL -------------------

    /**
     * @notice Used to check if the specified address is listed as a collaborator of the given collection's parameter.
     * @param collaborator Address to be checked.
     * @param collection Address of the collection.
     * @return isCollaborator_ Boolean value indicating if the address is a collaborator of the given collection's (`true`) or not
     *  (`false`).
     */
    function isCollaborator(
        address collaborator,
        address collection
    ) external view returns (bool isCollaborator_);

    /**
     * @notice Used to check if the specified address is listed as a specific address of the given collection's
     *  parameter.
     * @param specificAddress Address to be checked.
     * @param collection Address of the collection.
     * @param key The key of the attribute
     * @return isSpecificAddress_ Boolean value indicating if the address is a specific address of the given collection's parameter
     *  (`true`) or not (`false`).
     */
    function isSpecificAddress(
        address specificAddress,
        address collection,
        string memory key
    ) external view returns (bool isSpecificAddress_);

    /**
     * @notice Used to register a collection to use the RMRK token attributes repository.
     * @dev  If the collection does not implement the Ownable interface, the `useOwnable` value must be set to `false`.
     * @dev Emits an {AccessControlRegistration} event.
     * @param collection The address of the collection that will use the RMRK token attributes repository.
     * @param owner The address of the owner of the collection.
     * @param useOwnable The boolean value to indicate if the collection implements the Ownable interface and whether it
     *  should be used to validate that the caller is the owner (`true`) or to use the manually set owner address
     *  (`false`).
     */
    function registerAccessControl(
        address collection,
        address owner,
        bool useOwnable
    ) external;

    /**
     * @notice Used to manage the access control settings for a specific parameter.
     * @dev Only the `owner` of the collection can call this function.
     * @dev The possible `accessType` values are:
     *  [
     *      Owner,
     *      Collaborator,
     *      OwnerOrCollaborator,
     *      TokenOwner,
     *      SpecificAddress,
     *  ]
     * @dev Emits an {AccessControlUpdated} event.
     * @param collection The address of the collection being managed.
     * @param key The key of the attribute
     * @param accessType The type of access control to be applied to the parameter.
     * @param specificAddress The address to be added as a specific addresses allowed to manage the given
     *  parameter.
     */
    function manageAccessControl(
        address collection,
        string memory key,
        AccessType accessType,
        address specificAddress
    ) external;

    /**
     * @notice Used to manage the collaborators of a collection.
     * @dev The `collaboratorAddresses` and `collaboratorAddressAccess` arrays must be of the same length.
     * @dev Emits a {CollaboratorUpdate} event.
     * @param collection The address of the collection
     * @param collaboratorAddresses The array of collaborator addresses being managed
     * @param collaboratorAddressAccess The array of boolean values indicating if the collaborator address should
     *  receive the permission (`true`) or not (`false`).
     */
    function manageCollaborators(
        address collection,
        address[] memory collaboratorAddresses,
        bool[] memory collaboratorAddressAccess
    ) external;

    // ------------------- METADATA URI -------------------

    /**
     * @notice Used to retrieve the attributes metadata URI for a collection, which contains all the information about the collection attributes.
     * @param collection Address of the collection
     * @return attributesMetadataURI The URI of the attributes metadata
     */
    function getAttributesMetadataURIForCollection(
        address collection
    ) external view returns (string memory attributesMetadataURI);

    /**
     * @notice Used to set the metadata URI for a collection, which contains all the information about the collection attributes.
     * @dev Emits a {MetadataURIUpdated} event.
     * @param collection Address of the collection
     * @param attributesMetadataURI The URI of the attributes metadata
     */
    function setAttributesMetadataURIForCollection(
        address collection,
        string memory attributesMetadataURI
    ) external;

    // ------------------- GETTERS -------------------

    /**
     * @notice Used to retrieve the address type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the address attribute
     */
    function getAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (address attribute);

    /**
     * @notice Used to retrieve the bool type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the bool attribute
     */
    function getBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bool attribute);

    /**
     * @notice Used to retrieve the bytes type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the bytes attribute
     */
    function getBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bytes memory attribute);

    /**
     * @notice Used to retrieve the uint type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the uint attribute
     */
    function getUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (uint256 attribute);
    /**
     * @notice Used to retrieve the string type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the string attribute
     */
    function getStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (string memory attribute);

    /**
     * @notice Used to retrieve the int type token attributes.
     * @param collection The collection address
     * @param tokenId The token ID
     * @param key The key of the attribute
     * @return attribute The value of the uint attribute
     */
    function getIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (int256 attribute);

    // ------------------- BATCH GETTERS -------------------

    /**
     * @notice Used to get multiple address parameter values for a token.
     * @dev The `AddressAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     address value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of address keys to retrieve
     * @return attributes An array of addresses, in the same order as the attribute keys
     */
    function getAddressAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (address[] memory attributes);

    /**
     * @notice Used to get multiple bool parameter values for a token.
     * @dev The `BoolAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     bool value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of bool keys to retrieve
     * @return attributes An array of bools, in the same order as the attribute keys
     */
    function getBoolAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (bool[] memory attributes);

    /**
     * @notice Used to get multiple bytes parameter values for a token.
     * @dev The `BytesAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     bytes value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of bytes keys to retrieve
     * @return attributes An array of bytes, in the same order as the attribute keys
     */
    function getBytesAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (bytes[] memory attributes);

    /**
     * @notice Used to get multiple int parameter values for a token.
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of int keys to retrieve
     * @return attributes An array of ints, in the same order as the attribute keys
     */
    function getIntAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (int256[] memory attributes);

    /**
     * @notice Used to get multiple sting parameter values for a token.
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of string keys to retrieve
     * @return attributes An array of strings, in the same order as the attribute keys
     */
    function getStringAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (string[] memory attributes);

    /**
     * @notice Used to get multiple uint parameter values for a token.
     * @param collections Addresses of the collections, in the same order as the attribute keys. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attribute keys. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributeKeys An array of uint keys to retrieve
     * @return attributes An array of uints, in the same order as the attribute keys
     */
    function getUintAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        string[] memory attributeKeys
    ) external view returns (uint256[] memory attributes);

    /**
     * @notice Used to retrieve multiple token attributes of any type at once.
     * @dev The `StringAttribute`, `UintAttribute`, `IntAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists
     *  to the following fields (where `value` is of the appropriate type):
     *  [
     *      key,
     *      value,
     *  ]
     * @param collection The collection address
     * @param tokenId The token ID
     * @param addressKeys An array of address type attribute keys to retrieve
     * @param boolKeys An array of bool type attribute keys to retrieve
     * @param bytesKeys An array of bytes type attribute keys to retrieve
     * @param intKeys An array of int type attribute keys to retrieve
     * @param stringKeys An array of string type attribute keys to retrieve
     * @param uintKeys An array of uint type attribute keys to retrieve
     * @return addressAttributes An array of addresses, in the same order as the addressKeys
     * @return boolAttributes An array of bools, in the same order as the boolKeys
     * @return bytesAttributes An array of bytes, in the same order as the bytesKeys
     * @return intAttributes An array of ints, in the same order as the intKeys
     * @return stringAttributes An array of strings, in the same order as the stringKeys
     * @return uintAttributes An array of uints, in the same order as the uintKeys
     */
    function getAttributes(
        address collection,
        uint256 tokenId,
        string[] memory addressKeys,
        string[] memory boolKeys,
        string[] memory bytesKeys,
        string[] memory intKeys,
        string[] memory stringKeys,
        string[] memory uintKeys
    )
        external
        view
        returns (
            address[] memory addressAttributes,
            bool[] memory boolAttributes,
            bytes[] memory bytesAttributes,
            int256[] memory intAttributes,
            string[] memory stringAttributes,
            uint256[] memory uintAttributes
        );

    // ------------------- PREPARE PRESIGNED MESSAGES -------------------

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned address attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        address value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned bool attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bool value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned bytes attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned int attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned string attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value,
        uint256 deadline
    ) external view returns (bytes32 message);

    /**
     * @notice Used to retrieve the message to be signed for submitting a presigned uint attribute change.
     * @param collection The address of the collection smart contract of the token receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid
     * @return message Raw message to be signed by the authorized account
     */
    function prepareMessageToPresignUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value,
        uint256 deadline
    ) external view returns (bytes32 message);

    // ------------------- SETTERS -------------------

    /**
     * @notice Used to set an address attribute.
     * @dev Emits a {AddressAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setAddressAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        address value
    ) external;

    /**
     * @notice Used to set a boolean attribute.
     * @dev Emits a {BoolAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setBoolAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bool value
    ) external;

    /**
     * @notice Used to set an bytes attribute.
     * @dev Emits a {BytesAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setBytesAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value
    ) external;

    /**
     * @notice Used to set a signed number attribute.
     * @dev Emits a {IntAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setIntAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value
    ) external;

    /**
     * @notice Used to set a string attribute.
     * @dev Emits a {StringAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setStringAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value
    ) external;

    /**
     * @notice Used to set an unsigned number attribute.
     * @dev Emits a {UintAttributeUpdated} event.
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The token ID
     * @param key The attribute key
     * @param value The attribute value
     */
    function setUintAttribute(
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value
    ) external;

    // ------------------- BATCH SETTERS -------------------

    /**
     * @notice Sets multiple address attributes for a token at once.
     * @dev The `AddressAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      address value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `AddressAttribute` structs to be assigned to the given token
     */
    function setAddressAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        AddressAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple bool attributes for a token at once.
     * @dev The `BoolAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      bool value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `BoolAttribute` structs to be assigned to the given token
     */
    function setBoolAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        BoolAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple bytes attributes for a token at once.
     * @dev The `BytesAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      bytes value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `BytesAttribute` structs to be assigned to the given token
     */
    function setBytesAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        BytesAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple int attributes for a token at once.
     * @dev The `UintAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      int value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `IntAttribute` structs to be assigned to the given token
     */
    function setIntAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        IntAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple string attributes for a token at once.
     * @dev The `StringAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      string value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `StringAttribute` structs to be assigned to the given token
     */
    function setStringAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        StringAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple uint attributes for a token at once.
     * @dev The `UintAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      uint value
     *  ]
     * @param collections Addresses of the collections, in the same order as the attributes. If all tokens are from the same collection the array can contain a single element with the collection address.
     * @param tokenIds IDs of the tokens, in the same order as the attributes. If all attributes are for the same token the array can contain a single element with the token ID.
     * @param attributes An array of `UintAttribute` structs to be assigned to the given token
     */
    function setUintAttributes(
        address[] memory collections,
        uint256[] memory tokenIds,
        UintAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple attributes of multiple types for a token at the same time.
     * @dev Emits a separate event for each attribute set.
     * @dev The `StringAttribute`, `UintAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists
     *  to the following fields (where `value` is of the appropriate type):
     *  [
     *      key,
     *      value,
     *  ]
     * @param collection The address of the collection
     * @param tokenId The token ID
     * @param addressAttributes An array of `AddressAttribute` structs containing address attributes to set
     * @param boolAttributes An array of `BoolAttribute` structs containing bool attributes to set
     * @param bytesAttributes An array of `BytesAttribute` structs containing bytes attributes to set
     * @param intAttributes An array of `IntAttribute` structs containing int attributes to set
     * @param stringAttributes An array of `StringAttribute` structs containing string attributes to set
     * @param uintAttributes An array of `UintAttribute` structs containing uint attributes to set
     */
    function setAttributes(
        address collection,
        uint256 tokenId,
        AddressAttribute[] memory addressAttributes,
        BoolAttribute[] memory boolAttributes,
        BytesAttribute[] memory bytesAttributes,
        IntAttribute[] memory intAttributes,
        StringAttribute[] memory stringAttributes,
        UintAttribute[] memory uintAttributes
    ) external;

    // ------------------- PRESIGNED SETTERS -------------------

    /**
     * @notice Used to set the address attribute on behalf of an authorized account.
     * @dev Emits a {AddressAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetAddressAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        address value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Used to set the bool attribute on behalf of an authorized account.
     * @dev Emits a {BoolAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetBoolAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        bool value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Used to set the bytes attribute on behalf of an authorized account.
     * @dev Emits a {BytesAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetBytesAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        bytes memory value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Used to set the int attribute on behalf of an authorized account.
     * @dev Emits a {IntAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetIntAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        int256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Used to set the string attribute on behalf of an authorized account.
     * @dev Emits a {StringAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetStringAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        string memory value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Used to set the uint attribute on behalf of an authorized account.
     * @dev Emits a {UintAttributeUpdated} event.
     * @param setter Address of the account that presigned the attribute change
     * @param collection Address of the collection receiving the attribute
     * @param tokenId The ID of the token receiving the attribute
     * @param key The attribute key
     * @param value The attribute value
     * @param deadline The deadline timestamp for the presigned transaction
     * @param v `v` value of an ECDSA signature of the presigned message
     * @param r `r` value of an ECDSA signature of the presigned message
     * @param s `s` value of an ECDSA signature of the presigned message
     */
    function presignedSetUintAttribute(
        address setter,
        address collection,
        uint256 tokenId,
        string memory key,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}

Schema

In addition to the interface we propose that collection owers SHOULD be able to set the schema for the attributes of their collection. We distinguish between 2 types: token attributes and collection attributes. The latter are the attributes that are shared by all tokens in the collection, they can be retrieved and set by using the max uint256 value as tokenId.

For each attribute we specificy the following fields: name, description, type, display_name, display_type, decimals, min_value, max_value, conditional_value, modifiers and multi_storage. Only name and type are required. For more details on the fields, please refer to the [JSON schema] avaliable collection-metadata-schema.json, with an example available at collection-metadata-example.json.

The reasoning for conditional_value, modifiers and multi_storage will be discussed in the rationale section.

Message format for presigned attribute

The message to be signed by the setter in order for the attribute setting to be submitted by someone else is formatted as follows:

keccak256(
        abi.encode(
            DOMAIN_SEPARATOR,
            METHOD_TYPEHASH,
            collection,
            tokenId,
            key,
            value,
            deadline
        )
    );

The values passed when generating the message to be signed are:

  • DOMAIN_SEPARATOR - The domain separator of the Attribute repository smart contract
  • METHOD_TYPEHASH - The typehash of the method being called. The supported values, depending on the method are:
    • SET_UINT_ATTRIBUTE_TYPEHASH - Used for setting uint attributes
    • SET_STRING_ATTRIBUTE_TYPEHASH - Used for setting string attributes
    • SET_BOOL_ATTRIBUTE_TYPEHASH - Used for setting bool attributes
    • SET_BYTES_ATTRIBUTE_TYPEHASH - Used for setting bytes attributes
    • SET_ADDRESS_ATTRIBUTE_TYPEHASH - Used for setting address attributes
  • collection - Address of the collection containing the token receiving the attribute
  • tokenId - ID of the token receiving the attribute
  • key - The attribute key
  • value - The attribute value of the appropriate type
  • deadline - UNIX timestamp of the deadline for the signature to be submitted. The signed message submitted after the deadline MUST be rejected

The DOMAIN_SEPARATOR is generated as follows:

keccak256(
    abi.encode(
        "ERC-7508: Public Non-Fungible Token Attributes Repository",
        "1",
        block.chainid,
        address(this)
    )
);

The SET_UINT_ATTRIBUTE_TYPEHASH is generated as follows:

keccak256(
    "setUintAttribute(address collection,uint256 tokenId,string memory key,uint256 value)"
);

The SET_STRING_ATTRIBUTE_TYPEHASH is generated as follows:

keccak256(
    "setStringAttribute(address collection,uint256 tokenId,string memory key,string memory value)"
);

The SET_BOOL_ATTRIBUTE_TYPEHASH is generated as follows:

keccak256(
    "setBoolAttribute(address collection,uint256 tokenId,string memory key,bool value)"
);

The SET_BYTES_ATTRIBUTE_TYPEHASH is generated as follows:

keccak256(
    "setBytesAttribute(address collection,uint256 tokenId,string memory key,bytes memory value)"
);

The SET_ADDRESS_ATTRIBUTE_TYPEHASH is generated as follows:

keccak256(
    "setAddressAttribute(address collection,uint256 tokenId,string memory key,address value)"
);

Each chain, that the Attributes repository smart contract is deployed in, will have a different DOMAIN_SEPARATOR value due to chain IDs being different.

Pre-determined address of the Attributes repository

The address of the Emotable repository smart contract is designed to resemble the function it serves. It starts with 0xA77B75 which is the abstract representation of ATTBTS. The address is TBD.

Rationale

Designing the proposal, we considered the following questions:

  1. Should we refer to the values stored by the repository as propertiers or attributes?
    Historically values defining characteristics of tokens have been called properties, but have evolved in to being called attributes. Referring to the dictionary, the property is defined as a quality or characteristic that something has, and the attribute is defined as a quality or feature of somebody/something. We felt that using the term attribute fits better and decided to use it.
  2. Should the proposal specify access control?
    Designing the proposal, we had two options: either to include the access control within the specification of the proposal or to leave the access control up to the implementers that desire to use the attributes repository. While considering this we also had to consider the usability and compatibility aspects of the repository.
    On one hand, including access control narrows down the freedom of implementation and requires the implementers to configure it before being able to use the repository. On the other hand, leaving access control up to implementers requires dedicated design of attributes access control within their smart contracts, increasing their size, complexity and deployment costs.
    Another important thing to note is that including access control in the proposal makes it compatible with collections existing prior to the deployment of the repository and thus powers backwards-compatibility.
  3. Should the proposal establish an attributes extension or a common-good repository?
    Initially we set out to create an attributes extension to be used with any ERC-721 compliant tokens. However, we realized that the proposal would be more useful if it was a common-good repository of token attributes. This way, the tokens that can utilize it are not only the new ones but also the old ones that have been around since before the proposal.
    An additional benefit of this course-correction is the compatibility with ERC-1155 tokens.
  4. Should we include only single-action operations, only multi-action operations, or both?
    We’ve considered including only single-action operations, where the user is only able to assign a single attribute to a single token, but we decided to include both single-action and multi-action operations. This way, the users can choose whether they want to assign an attribute to a single token or on multiple tokens at once.
    This decision was made for the long-term viability of the proposal. Based on the gas cost of the network and the number of tokens in the collection, the user can choose the most cost-effective way of attribute assigning.
  5. Should we add the ability to assign attributes on someone else’s behalf?
    While we did not intend to add this as part of the proposal when drafting it, we realized that it would be a useful feature for it. This way, the users can assign attributes on behalf of someone else, for example, if they are not able to do it themselves or if the attribute is earned through an off-chain activity.
  6. How do we ensure that attribute assignment on someone else’s behalf is legitimate?
    We could add delegates to the proposal; when a user delegates their right to assign attributes to someone else, but having the ability to do so opens up the possibility for abuse and improper setting of attributes.
    Using ECDSA signatures, we can ensure that the user has given their consent to assign attribute on their behalf. This way, the user can sign a message with the parameters of the attribute and the signature can be submitted by someone else.
  7. Should we add chain ID as a parameter when assigning attribute to a token?
    We decided against this as we feel that additional parameter would rarely be used and would add additional cost to the attribute assignment transactions. If the collection smart contract wants to utilize on-chain token attributes, it requires the reactions to be recorded on the same chain. Marketplaces and wallets integrating this proposal will rely on attributes to reside in the same chain as well, because if chain ID parameter was supported this would mean that they would need to query the repository smart contract on all of the chains the repository is deployed in order to get the attributes of a given token.
    Additionally, if the collection creator wants users to record their reactions on a different chain, they can still direct the users to do just that. The repository does not validate the existence of the token being reacted to (except in an instace where the attribute can be modified by the token’s owner), which in theory means that you can assign an attribute to non-existent token or to a token that does not exist yet.
  8. How should we reduce the cost of string usage in the repository? One fo the main issues we were dealing with while designing the proposal is the cost of string usage. We considered using bytes instead of strings, but decided against it as it would require the users to encode and decode the strings themselves.
    The solution for reducing the cost was to use a string indices. This means that the cost of setting a new string attribute or key will only be paid by the first user to do so. The subsequent users will only pay the cost of setting the index of the string attribute or key.
    We also extended this gas-saving approach to be applicable across the entire repository. This means that if the string was already set by one collection, any other collection using the same string will not have to pay the cost of setting the string again.

Backwards Compatibility

The Attributes repository standard is fully compatible with ERC-721 and ERC-1155 and with the robust tooling available for implementations of ERC-721 as well as with the existing ERC-721 infrastructure.

Test Cases

Tests are included in attributesRepository.ts.

To run them in terminal, you can use the following commands:

cd ../assets/eip-7508
pnpm i
pnpm hardhat test

Reference Implementation

See AttributesRepository.sol.

Security Considerations

The proposal does not envision handling any form of assets from the user, so the assets should not be at risk when interacting with an Attributes repository.

The ability to use ECDSA signatures to set attributes on someone else’s behalf introduces the risk of a replay attack, which the format of the message to be signed guards against. The DOMAIN_SEPARATOR used in the message to be signed is unique to the repository smart contract of the chain it is deployed on. This means that the signature is invalid on any other chain and the attributes repositories deployed on them should revert the operation if a replay attack is attempted.

Another thing to consider is the ability of presigned message reuse. Since the message includes the signature validity deadline, the message can be reused any number of times before the deadline is reached. The proposal only allows for a single value for a given key to be set, so the presigned message can not be abused to further modify the attribute value. However, if the service using the repository relies on the ability to revert or modify the attribute after certain actions, a valid presigned message can be used to re-assign the attribute of the token. We suggest that the services using the repository in cnjunction with presigned messages use deadlines that invalidate presigned messages after a reasonalby short period of time.

Caution is advised when dealing with non-audited contracts.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer), "ERC-7508: Dynamic On-Chain Token Attributes Repository [DRAFT]," Ethereum Improvement Proposals, no. 7508, August 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7508.