📀Specification

Overview

The Event-Driven Utilities Standard streamlines the integration and update process of NFTs within diverse applications on the Ethereum blockchain. The process flow is 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 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 these NFTs is managed separately within each application environment, maintaining autonomy and non-interference across applications.

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:

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

/**
 * @title IEventDrivenUtilities
 * Interface for the Event-Driven NFT Utilities Standard for dynamic and interoperable NFT updates.
 */
interface IEventDrivenUtilities {
    /*
     * EVENTS
     */

    /// 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 for updates.
    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 information related to an application is changed.
    event InfoChanged(uint256 indexed appId, string newInfo); 
    
    /// MUST be emitted for any status update within an application that affects its NFTs.
    event AppStatusUpdated(uint256 indexed appId, string updateUrl);

    /*
     * FUNCTIONS
     */
    
    /// Registers an application, initializing it for dynamic and interoperable NFT updates.
    function registerApp(string calldata appInfoUrl) external returns (uint256 newAppId);
    
    /// Associates NFT collections with an application for updates.
    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 NFTs.
    function updateAppStatus(uint256 appId, string calldata updateUrl) external;
}

Sample JSON Schema

To illustrate the kind of content updateModuleUrl MAY points 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": {
        "1_0x0c1AfA2d6D05da2BE8E73CBA2398cf09a530e2B4": [0,1,2],
        "1_0xAADBA53eB120A1f60064aa4443a71BDf81C8Cc34": [3,5],
        "42161_0xF0240b006e324D76cC12191DFd22b239927ecC16": [0,1,2,3,4,5],
        "56_0xC36e927e9a28dfc80AF62019a2890dDaAe038a63": [0,1],
        "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 (e.g., HP, MP, EXP, etc.). It also demonstrates how NFT collections, identified by their chain ID and contract address, can have specific attributes selected.

For example, the key-value pair

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

specifies the attributes that an NFT collection utilizes based on the global attributes array. The 8453 segment denotes the chain ID of the blockchain network where the NFT collection's smart contract is deployed, with 8453 representing Base Mainnet. 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 that are relevant to this NFT collection, signifying that the NFTs within this collection utilize the attributes EXP, DEF, and SPEED for their dynamic updates.

This schema is critical for clarifying the expected update mechanism, promoting consistent and flexible NFT attribute management, and ensuring interoperability across different blockchain networks within the multi-chain Ethereum ecosystem.

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);
    }
}

Last updated