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:
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.
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.
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: MITpragmasolidity ^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.eventAppRegistered(uint256indexed appId, addressindexed appOwnerAddr, string appInfo);/// MUST be emitted when an NFT collection is registered under an application for updates.eventCollectionRegistered(uint256indexed appId, uint256indexed chainId, addressindexed collectionAddr);/// MUST be emitted when an update module is set for an applicationeventUpdateModuleSet(uint256indexed appId, string updateModuleUrl);/// MUST be emitted when the ownership of an application is transferred.eventOwnerTransferred(uint256indexed appId, address newOwner);/// MUST be emitted when information related to an application is changed.eventInfoChanged(uint256indexed appId, string newInfo); /// MUST be emitted for any status update within an application that affects its NFTs.eventAppStatusUpdated(uint256indexed appId, string updateUrl);/* * FUNCTIONS *//// Registers an application, initializing it for dynamic and interoperable NFT updates.functionregisterApp(stringcalldata appInfoUrl) externalreturns (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.functionsetAppUpdateModule(uint256 appId,stringcalldata updateModuleUrl) external;/// OPTIONAL: Transfers the ownership of an application to a new owner.functiontransferAppOwner(uint256 appId,address newOwner) external;/// OPTIONAL: Changes the informational details of an application.functionchangeAppInfo(uint256 appId,stringcalldata newInfo) external;/// Facilitates a status update within an application, affecting its NFTs.functionupdateAppStatus(uint256 appId,stringcalldata 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:
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.
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: MITpragmasolidity ^0.8.0;import"./IEventDrivenUtilities.sol";/** * @title EventDrivenUtilities * A fundamental implementation of the Event-Driven NFT Utilities Standard. */contractEventDrivenUtilitiesisIEventDrivenUtilities {/* * STATE VARIABLES */// appId iteratoruint256private _appIdIterator =0;// appId => Application owner addressmapping(uint256=>address) public appOwner;// appId => Application information URLmapping(uint256=>string) public appInfo;// appId => Application update module URLmapping(uint256=>string) public appUpdateModule;// appId => NFT collection address arraymapping(uint256=>address[]) public appRegisteredCollections;// appId => NFT collection address => boolmapping(uint256=>mapping(address=>bool)) public appIsCollectionRegistered;/* * MODIFIERS */// Modifier that checks if the caller is the owner of the applicationmodifieronlyAppOwner(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. */functionregisterApp(stringcalldata appInfoUrl) externalreturns (uint256 newAppId) { newAppId =++_appIdIterator; appOwner[newAppId] = msg.sender; appInfo[newAppId] = appInfoUrl;emitAppRegistered(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. */functionregisterCollections(uint256 appId,uint256[] calldata chainIds,address[] calldata collectionAddrs)externalonlyAppOwner(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-onefor (uint256 i =0; i < collectionAddrs.length; i++) {if (!appIsCollectionRegistered[appId][collectionAddrs[i]]) { appRegisteredCollections[appId].push(collectionAddrs[i]); appIsCollectionRegistered[appId][collectionAddrs[i]] =true;emitCollectionRegistered(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. */functionsetAppUpdateModule(uint256 appId,stringcalldata updateModuleUrl) externalonlyAppOwner(appId) { appUpdateModule[appId] = updateModuleUrl;emitUpdateModuleSet(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. */functionupdateAppStatus(uint256 appId,stringcalldata updateUrl) externalonlyAppOwner(appId) {require(bytes(appUpdateModule[appId]).length >0,"Update module of the application has not been set.");emitAppStatusUpdated(appId, updateUrl); }}