ERC-7672: Event-Driven NFT Utilities

Abstract

This proposal introduces a standard for enabling dynamic status changes of a Non-Fungible Token (NFT) in different applications. There are 2 main features introduced:

  1. Instead of modifying the NFT metadata directly on-chain, this approach employs an event-driven mechanism that represents status changes through the emission of on-chain events.
  2. Instead of limiting an NFT to a specific application, this approach facilitates distinct utilities of an NFT (even an existing one like a Doodle) across multiple applications.

Any application following this standard effectively possesses a chain of events where each event corresponds to a distinct application status. Note that such an event contains only status changes to the application, which may be a single-bit change of an NFT status like “Health Point +1” for “Doodle #9999” within a specific application.

Therefore, an NFT will have a distinct representation in each supporting application (e.g. a mighty hero in one game; an ordinary pet in a social app; or a ticket with changing access levels in an event platform), corresponding to its unique NFT utility within this application. This standard supports efficient management of application-NFT statuses and NFT interoperability across various applications, significantly broadening NFT utilities.

Motivation

The rapid growth of the NFT ecosystem has unveiled a multitude of new opportunities for creators, collectors, and developers. However, this growth has also highlighted significant limitations in the current standards for NFTs, particularly regarding their dynamic interaction with decentralized applications (dApps). Presently, NFTs are often static and confined to the context in which they were created. This lack of flexibility limits the potential use cases and the overall utilities of NFTs within the broader blockchain ecosystem.

Several key challenges motivate this proposal:

  1. Static Nature of NFTs: Once minted, the metadata of most NFTs, including their attributes and utilities, remains unchanged. This rigidity prevents NFTs from evolving over time or interacting dynamically with various dApps.
  2. Application-Specific Limitations: NFTs are frequently designed and utilized within specific applications or ecosystems. This approach restricts the NFT’s utility and value to its original context, hindering cross-application interoperability and innovation.
  3. Efficiency and Scalability Concerns: Directly modifying NFT metadata on-chain for status updates is inefficient and can lead to increased transaction costs and scalability issues. This approach is not sustainable for applications requiring frequent updates.

This proposal aims to address these challenges by introducing a standard for dynamic status changes of NFTs in different applications. By leveraging an event-driven mechanism for signaling status updates and facilitating NFT utilities across multiple applications, this proposal seeks to enhance the usability, efficiency, interoperability, and overall utilities of NFTs within the Ethereum ecosystem. The introduction of a flexible and standardized approach for dynamic application-NFT interactions promises to unlock new possibilities for creators, developers, and users, paving the way for innovative applications and use cases that extend beyond the current limitations.

Specification

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

Overview

The Event-Driven NFT Utilities Standard streamlines the integration and update process of NFT statuses within diverse applications. This section outlines the key functionalities and flow of the system as follows:

  1. Application Registration: Application owners initiate the process by registering their application with the Event-Driven Utilities Smart Contract. Post-registration, each application is associated with an “Update Module”. The application owner specifies this module by providing a URL pointing to the content of the “Update Module” stored on decentralized storage (e.g., https://ipfs.io/ipfs/QmeSjS...). This module sets out the global attributes for the NFTs and the update mechanisms within the application.
  2. NFT Collection Registration: Upon successful application registration, a unique identifier is assigned to the application. The application owner can then proceed to register multiple NFT collections under this application across different blockchains by providing the respective chain ID and NFT smart contract address. This registration is performed via a function call to the smart contract, ensuring the NFT collections are recognized for use and updates within the application environment.
  3. Status Updates Through Events: Once the application and NFT collections are registered, the application owner can initiate status updates. This is done by emitting events that signal status changes within the application for the updated tokens. The bottom of the diagram illustrates the independent update processes for different applications, allowing them to update their respective statuses through events. This design ensures efficiency and that while applications MAY share NFT collections, the status of an NFT is managed separately within each application, maintaining autonomy and non-interference across applications. Effectively, each application possesses a chain of events representing the progression of applicaiton statuses.

Smart Contract Interface

The interface for our Event-Driven NFT Utilities Standard, which establishes the foundational structure for implementing the proposed standard, is provided below:

/**
 * @title IEventDrivenUtilities
 * Interface for the Event-Driven NFT Utilities Standard
 */
interface IEventDrivenUtilities {
    /// MUST be emitted upon the registration of an application.
    event AppRegistered(uint256 indexed appId, address indexed appOwnerAddr, string appInfo);
    /// MUST be emitted when an NFT collection is registered under an application.
    event CollectionRegistered(uint256 indexed appId, uint256 indexed chainId, address indexed collectionAddr);
    /// MUST be emitted when an update module is set for an application
    event UpdateModuleSet(uint256 indexed appId, string updateModuleUrl);
    /// MUST be emitted when the ownership of an application is transferred.
    event OwnerTransferred(uint256 indexed appId, address newOwner);
    /// MUST be emitted when the information related to an application is changed.
    event InfoChanged(uint256 indexed appId, string newInfo); 
    /// MUST be emitted for any status updates within an application.
    event AppStatusUpdated(uint256 indexed appId, string updateUrl);
    
    /// Registers an application, initializing it for dynamic NFT status updates.
    function registerApp(string calldata appInfoUrl) external returns (uint256 newAppId);
    /// Specifies which NFT collection(s) to support in a registered application.
    function registerCollections(uint256 appId, uint256[] calldata chainIds, address[] calldata collectionAddrs) external;
    /// Configures an update module for an application, detailing the update mechanism.
    function setAppUpdateModule(uint256 appId, string calldata updateModuleUrl) external;
    /// OPTIONAL: Transfers the ownership of an application to a new owner.
    function transferAppOwner(uint256 appId, address newOwner) external;
    /// OPTIONAL: Changes the informational details of an application.
    function changeAppInfo(uint256 appId, string calldata newInfo) external;
    /// Facilitates a status update within an application, affecting its NFT statuses and thus the application status.
    function updateAppStatus(uint256 appId, string calldata updateUrl) external;
}

Sample JSON Schema

To illustrate the kind of content updateModuleUrl MAY point to, below is a sample JSON schema. This schema specifies the update mechanism, known as the update module, showcasing a potential implementation:

{
    "applicationId": 1,
    "applicationName": "GTA V",
    "description": "The update module for App 1: GTA V",
    "global_attributes": ["HP", "MP", "EXP", "ATK", "DEF", "SPEED"],
    "collections": {
        "Chain-1_0x0c1AfA2d6D05da2BE8E73CBA2398cf09a530e2B4": [0,1,2], // i.e. ["HP", "MP", "EXP"]
        "Chain-1_0xAADBA53eB120A1f60064aa4443a71BDf81C8Cc34": [3,5], // i.e. ["ATK", "SPEED"]
        "Chain-42161_0xF0240b006e324D76cC12191DFd22b239927ecC16": [0,1,2,3,4,5],
        "Chain-56_0xC36e927e9a28dfc80AF62019a2890dDaAe038a63": [0,1],
        "Chain-8453_0x812d249854EaaD98E50e6a3Fe4033C575925E4aC": [2,4,5]
    }
}

The schema illustrates key components such as applicationId, applicationName, and a list of global_attributes that an application might want to update (HP, MP, EXP, etc.). It also demonstrates how an NFT collection, identified by its chain ID and contract address, can have collection-specific attributes (subset of the global_attributes) related to status updates. For example, the key-value pair

"Chain-8453_0x812d249854EaaD98E50e6a3Fe4033C575925E4aC": [2,4,5]

specifies the attributes an NFT collection corresponds to within the application based on the global_attributes array. The 8453 segment denotes the chain ID of the blockchain network (Base Mainnet in this case) where the NFT collection’s smart contract is deployed. The hexadecimal sequence 0x0c1AfA2d6D05da2BE8E73CBA2398cf09a530e2B4 is the smart contract address for the NFT collection on that specific chain. The array [2,4,5] indicates the indices of the global_attributes array this NFT collection corresponds to, signifying that the NFTs of this collection corresponds to the attributes EXP, DEF, and SPEED for their dynamic status updates.

This schema is critical for clarifying the expected update mechanism, promoting a consistent and flexible approach to application-NFT status management, and ensuring interoperability within the multi-chain environment.

Rationale

Event-Driven Mechanism

The decision to implement an event-driven mechanism for signaling status changes of an NFT is rooted in the pursuit of efficiency, scalability, and interoperability across different applications. Traditional methods of updating NFT metadata or attributes directly on-chain not only are costly in terms of transaction fees but also introduce challenges in terms of scalability, especially for applications that require frequent status updates. By leveraging on-chain events to represent status changes, this approach minimizes the need for direct modifications to the NFT’s on-chain metadata, significantly reducing transaction costs and the burden on the blockchain.

Furthermore, the event-driven architecture enhances the flexibility and interoperability of NFTs. It allows for the dynamic representation of an NFT’s status across multiple applications without altering its inherent properties. This mechanism supports a broad range of use cases, enabling NFTs to serve different roles and functions in various applications. For example, an NFT could represent a character with evolving attributes in a game, or act as a ticket with changing access levels in a decentralized event platform. The adoption of an event-driven model thus represents a strategic move towards a more vibrant, functional, and interconnected ecosystem for NFTs.

Update Module

The introduction of the Update Module within the Event-Driven NFT Utilities Standard is a strategic choice aimed at maximizing flexibility and specificity in how NFT statuses are updated across applications. This modular approach allows each application to define its own unique set of rules and mechanisms for status updates, encapsulated within a URL pointing to decentralized storage. This design decision was made to ensure that updates are both transparent and verifiable by the community, adhering to the decentralized ethos of the blockchain space. Choosing decentralized storage for hosting the Update Module ensures that the instructions for status changes are immutable and accessible, fostering trust in the update mechanism. It also provides a scalable solution that can accommodate the diverse needs of different applications without congesting the blockchain with extensive data. This method avoids the limitations of a one-size-fits-all approach, enabling applications to tailor the update logic to their specific requirements and dynamics. The Update Module thereby plays a critical role in the standard, ensuring that NFT status updates are conducted in a consistent, secure, and application-specific manner.

Additionally, for those seeking a more formalized method to implement the update module, ERC-5185 offers a structured strategy that enables controlled NFT status updates through specific formulas. This method ensures deterministic and limited updates, which are verifiable through on-chain events, thereby making it suitable for applications that desire a methodical approach to NFT status changes. While ERC-5185 provides an option for scenarios that demand a defined and deterministic update process, this EIP fosters dynamic and application-specific updates for NFTs. Application owners are RECOMMENDED to consider ERC-5185 for implementing a structured update module.

Storage Module

The Event-Driven NFT Utilities Standard is designed to be agnostic regarding the choice of decentralized storage platforms, thereby granting developers the autonomy to select the one that best suits their application’s requirements. Supported platforms include, but are not limited to, Filecoin, Arweave, ETH Storage, BNB Greenfield, and others that might offer similar decentralized storage solutions. This flexibility ensures that the storage of update modules and related data can be tailored to the specific security, redundancy, and accessibility needs of each application.

Synergies with Other EIPs

This EIP is strategically crafted to complement and enhance existing EIPs, thereby boosting its utility and fostering greater interoperability across the Ethereum ecosystem. With a focus on a generalized approach, we promote the integration with and adaptation to a wide range of applications: games, social media, event platforms, and beyond. We intend for this EIP to serve as a versatile foundational layer, enabling seamless interaction with other EIPs, such as EIP-6551. By highlighting the potential for cooperation and mutual enhancement among EIPs, we demonstrate our dedication to nurturing a unified and expansive ecosystem that capitalizes on the collective progress and innovation within Ethereum.

Backwards Compatibility

This proposal seeks to be maximally backward-compatible with existing NFT collections. As such, it does not extend the ERC-721 or ERC-1155 standard.

Reference Implementation

A fundamental implementation of the EventDrivenUtilities Standard:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IEventDrivenUtilities.sol";

/**
 * @title EventDrivenUtilities
 * A fundamental implementation of the Event-Driven NFT Utilities Standard.
 */
contract EventDrivenUtilities is IEventDrivenUtilities {
    /*
     * STATE VARIABLES
     */

    // appId iterator
    uint256 private _appIdIterator = 0;

    // appId => Application owner address
    mapping(uint256 => address) public appOwner;
    // appId => Application information URL
    mapping(uint256 => string) public appInfo;
    // appId => Application update module URL
    mapping(uint256 => string) public appUpdateModule;
    // appId => NFT collection address array
    mapping(uint256 => address[]) public appRegisteredCollections;
    // appId => NFT collection address => bool
    mapping(uint256 => mapping(address => bool)) public appIsCollectionRegistered;

    /*
     * MODIFIERS
     */

    // Modifier that checks if the caller is the owner of the application
    modifier onlyAppOwner(uint256 appId) {
        require(appOwner[appId] == msg.sender, "Caller is not the owner of this application.");
        _;
    }

    /*
     * FUNCTIONS
     */

    /**
     * @notice  Registers an application and assigns a unique appId to it.
     * @dev     appId can be non-sequential through a deterministic collision-free hash to keep its uniqueness.
     *          We assign the _appIdIterator here as the appId for simplicity.
     * @param   appInfoUrl : URL containing information about the app.
     * @return  newAppId : The assigned appId.
     */
    function registerApp(string calldata appInfoUrl) external returns (uint256 newAppId) {
        newAppId = ++_appIdIterator;
        appOwner[newAppId] = msg.sender;
        appInfo[newAppId] = appInfoUrl;
        emit AppRegistered(newAppId, msg.sender, appInfoUrl);
    }

    /**
     * @notice  Registers an array of NFT collection(s) for an application.
     * @param   appId : The unique ID of the application.
     * @param   chainIds : An array of chain ID(s) of the NFT collection(s).
     * @param   collectionAddrs : An array of contract address(s) of the NFT collection(s).
     * @dev     May return the number of NFT collections registered.
     */
    function registerCollections(uint256 appId, uint256[] calldata chainIds, address[] calldata collectionAddrs)
        external
        onlyAppOwner(appId)
    {
        require(chainIds.length == collectionAddrs.length, "Array lengths of Chain ID and collection address mismatch.");
        // Register the array of NFT collection(s) to the application one-by-one
        for (uint256 i = 0; i < collectionAddrs.length; i++) {
            if (!appIsCollectionRegistered[appId][collectionAddrs[i]]) {
                appRegisteredCollections[appId].push(collectionAddrs[i]);
                appIsCollectionRegistered[appId][collectionAddrs[i]] = true;
                emit CollectionRegistered(appId, chainIds[i], collectionAddrs[i]);
            }
        }
    }

    /**
     * @dev transferAppOwner() and changeAppInfo() can be implemented in a similar way with their events
     * @notice  Sets the update module for an application.
     * @param   appId : The unique ID of the application.
     * @param   updateModuleUrl : URL containing information about the update module.
     */
    function setAppUpdateModule(uint256 appId, string calldata updateModuleUrl) external onlyAppOwner(appId) {
        appUpdateModule[appId] = updateModuleUrl;
        emit UpdateModuleSet(appId, updateModuleUrl);
    }

    /**
     * @notice  Emits an update event for an application.
     * @param   appId : The unique ID of the application.
     * @param   updateUrl : The URL of the update information.
     */
    function updateAppStatus(uint256 appId, string calldata updateUrl) external onlyAppOwner(appId) {
        require(bytes(appUpdateModule[appId]).length > 0, "Update module of the application has not been set.");
        emit AppStatusUpdated(appId, updateUrl);
    }
}

Security Considerations

Needs discussion.

Copyright

Copyright and related rights waived via CC0.