AlertSourceDiscuss
Skip to content

ERC-7508: Dynamic On-Chain Token Attributes Repository

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

⚠️ DraftERC

Draft Notice

This EIP is in the process of being drafted. The content of this EIP is not final and can change at any time; this EIP is not yet suitable for use in production. Thank you!

AuthorsSteven Pineda (@steven2308), Jan Turk (@ThunderDeliverer)
Created2023-08-15

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

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.

solidity
/// @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 0x07cd44c7.

pragma solidity ^0.8.21;

interface IRMRKTokenAttributesRepository /*is IERC165*/ {
    /**
     * @notice A list of supported access types.
     * @return The `Issuer` type, where only the issuer can manage the parameter.
     * @return The `Collaborator` type, where only the collaborators can manage the parameter.
     * @return The `IssuerOrCollaborator` type, where only the issuer 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 {
        Issuer,
        Collaborator,
        IssuerOrCollaborator,
        TokenOwner,
        SpecificAddress
    }

    /**
     * @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 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 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 bytes attribute.
     * @return key The key of the attribute
     * @return value The value of the attribute
     */
    struct BytesAttribute {
        string key;
        bytes value;
    }

    /**
     * @notice Used to notify listeners that a new collection has been registered to use the repository.
     * @param collection Address of the collection
     * @param issuer Address of the issuer 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
     *  issuer (`true`) or not (`false`)
     */
    event AccessControlRegistration(
        address indexed collection,
        address indexed issuer,
        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 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 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
    );

    /**
     * @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 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 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 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 issuer The address of the issuer 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 issuer (`true`) or to use the manually set issuer address
     *  (`false`).
     */
    function registerAccessControl(
        address collection,
        address issuer,
        bool useOwnable
    ) external;

    /**
     * @notice Used to manage the access control settings for a specific parameter.
     * @dev Only the `issuer` of the collection can call this function.
     * @dev The possible `accessType` values are:
     *  [
     *      Issuer,
     *      Collaborator,
     *      IssuerOrCollaborator,
     *      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;

    /**
     * @notice Used to set a 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;

    /**
     * @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 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 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 Sets multiple string attributes for a token at once.
     * @dev The `StringAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      string value
     *  ]
     * @param collection Address of the collection
     * @param tokenId ID of the token
     * @param attributes An array of `StringAttribute` structs to be assigned to the given token
     */
    function setStringAttributes(
        address collection,
        uint256 tokenId,
        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 collection Address of the collection
     * @param tokenId ID of the token
     * @param attributes An array of `UintAttribute` structs to be assigned to the given token
     */
    function setUintAttributes(
        address collection,
        uint256 tokenId,
        UintAttribute[] 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 collection Address of the collection
     * @param tokenId ID of the token
     * @param attributes An array of `BoolAttribute` structs to be assigned to the given token
     */
    function setBoolAttributes(
        address collection,
        uint256 tokenId,
        BoolAttribute[] memory attributes
    ) external;

    /**
     * @notice Sets multiple address attributes for a token at once.
     * @dev The `AddressAttribute` struct contains the following fields:
     *  [
     *      string key,
     *      address value
     *  ]
     * @param collection Address of the collection
     * @param tokenId ID of the token
     * @param attributes An array of `AddressAttribute` structs to be assigned to the given token
     */
    function setAddressAttributes(
        address collection,
        uint256 tokenId,
        AddressAttribute[] 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 collection Address of the collection
     * @param tokenId ID of the token
     * @param attributes An array of `BytesAttribute` structs to be assigned to the given token
     */
    function setBytesAttributes(
        address collection,
        uint256 tokenId,
        BytesAttribute[] 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 stringAttributes An array of `StringAttribute` structs containing string attributes to set
     * @param uintAttributes An array of `UintAttribute` structs containing uint attributes to set
     * @param boolAttributes An array of `BoolAttribute` structs containing bool attributes to set
     * @param addressAttributes An array of `AddressAttribute` structs containing address attributes to set
     * @param bytesAttributes An array of `BytesAttribute` structs containing bytes attributes to set
     */
    function setTokenAttributes(
        address collection,
        uint256 tokenId,
        StringAttribute[] memory stringAttributes,
        UintAttribute[] memory uintAttributes,
        BoolAttribute[] memory boolAttributes,
        AddressAttribute[] memory addressAttributes,
        BytesAttribute[] memory bytesAttributes
    ) 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;

    /**
     * @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 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 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 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 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);

    /**
     * @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 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);

    /**
     * @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 The value of the string attribute
     */
    function getStringTokenAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (string memory);

    /**
     * @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 The value of the uint attribute
     */
    function getUintTokenAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (uint256);

    /**
     * @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 The value of the bool attribute
     */
    function getBoolTokenAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bool);

    /**
     * @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 The value of the address attribute
     */
    function getAddressTokenAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (address);

    /**
     * @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 The value of the bytes attribute
     */
    function getBytesTokenAttribute(
        address collection,
        uint256 tokenId,
        string memory key
    ) external view returns (bytes memory);

    /**
     * @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 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);

    /**
     * @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 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);

    /**
     * @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 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);

    /**
     * @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 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);

    /**
     * @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 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);

    /**
     * @notice Used to retrieve multiple token attributes of any type at once.
     * @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 collection address
     * @param tokenId The token ID
     * @param stringKeys An array of string type attribute keys to retrieve
     * @param uintKeys An array of uint type attribute keys to retrieve
     * @param boolKeys An array of bool type attribute keys to retrieve
     * @param addressKeys An array of address type attribute keys to retrieve
     * @param bytesKeys An array of bytes type attribute keys to retrieve
     * @return stringAttributes An array of `StringAttribute` structs containing the string type attributes
     * @return uintAttributes An array of `UintAttribute` structs containing the uint type attributes
     * @return boolAttributes An array of `BoolAttribute` structs containing the bool type attributes
     * @return addressAttributes An array of `AddressAttribute` structs containing the address type attributes
     * @return bytesAttributes An array of `BytesAttribute` structs containing the bytes type attributes
     */
    function getTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory stringKeys,
        string[] memory uintKeys,
        string[] memory boolKeys,
        string[] memory addressKeys,
        string[] memory bytesKeys
    )
        external
        view
        returns (
            StringAttribute[] memory stringAttributes,
            UintAttribute[] memory uintAttributes,
            BoolAttribute[] memory boolAttributes,
            AddressAttribute[] memory addressAttributes,
            BytesAttribute[] memory bytesAttributes
        );

    /**
     * @notice Used to get multiple sting parameter values for a token.
     * @dev The `StringAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     string value
     *  ]
     * @param collection Address of the collection the token belongs to
     * @param tokenId ID of the token for which the attributes are being retrieved
     * @param stringKeys An array of string keys to retrieve
     * @return An array of `StringAttribute` structs
     */
    function getStringTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory stringKeys
    ) external view returns (StringAttribute[] memory);

    /**
     * @notice Used to get multiple uint parameter values for a token.
     * @dev The `UintAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     uint value
     *  ]
     * @param collection Address of the collection the token belongs to
     * @param tokenId ID of the token for which the attributes are being retrieved
     * @param uintKeys An array of uint keys to retrieve
     * @return An array of `UintAttribute` structs
     */
    function getUintTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory uintKeys
    ) external view returns (UintAttribute[] memory);

    /**
     * @notice Used to get multiple bool parameter values for a token.
     * @dev The `BoolAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     bool value
     *  ]
     * @param collection Address of the collection the token belongs to
     * @param tokenId ID of the token for which the attributes are being retrieved
     * @param boolKeys An array of bool keys to retrieve
     * @return An array of `BoolAttribute` structs
     */
    function getBoolTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory boolKeys
    ) external view returns (BoolAttribute[] memory);

    /**
     * @notice Used to get multiple address parameter values for a token.
     * @dev The `AddressAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     address value
     *  ]
     * @param collection Address of the collection the token belongs to
     * @param tokenId ID of the token for which the attributes are being retrieved
     * @param addressKeys An array of address keys to retrieve
     * @return An array of `AddressAttribute` structs
     */
    function getAddressTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory addressKeys
    ) external view returns (AddressAttribute[] memory);

    /**
     * @notice Used to get multiple bytes parameter values for a token.
     * @dev The `BytesAttribute` struct contains the following fields:
     *  [
     *     string key,
     *     bytes value
     *  ]
     * @param collection Address of the collection the token belongs to
     * @param tokenId ID of the token for which the attributes are being retrieved
     * @param bytesKeys An array of bytes keys to retrieve
     * @return An array of `BytesAttribute` structs
     */
    function getBytesTokenAttributes(
        address collection,
        uint256 tokenId,
        string[] memory bytesKeys
    ) external view returns (BytesAttribute[] memory);
}

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:

solidity
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:

solidity
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:

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

The SET_STRING_ATTRIBUTE_TYPEHASH is generated as follows:

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

The SET_BOOL_ATTRIBUTE_TYPEHASH is generated as follows:

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

The SET_BYTES_ATTRIBUTE_TYPEHASH is generated as follows:

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

The SET_ADDRESS_ATTRIBUTE_TYPEHASH is generated as follows:

solidity
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:

0xA77b75D5fDEC6E6e8E00e05c707a7CA81a3F9f4a

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
npm install
npx 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, Jan Turk, "ERC-7508: Dynamic On-Chain Token Attributes Repository[DRAFT]," Ethereum Improvement Proposals, no. 7508, 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7508.