This proposal recommends an extension of ERC-721 to allow for collateralization using a list of ERC-20 based tokens. The proprietor of this ERC collection could hold both the native coin and ERC-20 based tokens, with the ownerOf tokenId being able to unlock the associated portion of the underlying ERC-20 balance.
Motivation
The emerging trend of NFT finance focuses on the NFT floor price to enable the market value of the NFT serve as a collateral in lending protocols. The NFT floor price is susceptible to the supply-demand dynamics of the NFT market, characterized by higher volatility compared to the broader crypto market. Furthermore, potential price manipulation in specific NFT collections can artificially inflate NFT market prices, impacting the floor price considered by lending protocols. Relying solely on the NFT floor price based on market value is both unpredictable and unreliable.
This ERC addresses various challenges encountered by the crypto community with ERC-721 based collections and assets. This ERC brings forth advantages such as sustainable NFT royalties supported by tangible assets, an on-chain verifiable floor price, and the introduction of additional monetization avenues for NFT collection creators.
Presets
The Basic Preset allows for the evaluation of an on-chain verifiable price floor for a specified NFT asset.
The Dynamic Preset facilitates on-chain modification of tokenURI based on predefined collateral rules for a specified NFT asset.
With the Royalty Preset, NFT collection creators can receive royalty payments for each transaction involving asset owners and Externally Owned Accounts (EOA), as well as transactions with smart contracts.
The VRF Preset enables the distribution of collateral among multiple NFT asset holders using the Verifiable Random Function (VRF) by Chainlink.
Extension to Existing ERC-721 Based Collections
For numerous ERC-721 based collections that cannot be redeployed, we propose the implementation of an abstraction layer embodied by a smart contract. This smart contract would replicate all the functionalities of this ERC standard and grant access to collateral through mapping.
Specification
ERC standard for new NFT collections
interfaceIERC721EnviousisIERC721{eventCollateralized(uint256indexedtokenId,uint256amount,addresstokenAddress);eventUncollateralized(uint256indexedtokenId,uint256amount,addresstokenAddress);eventDispersed(addressindexedtokenAddress,uint256amount);eventHarvested(addressindexedtokenAddress,uint256amount,uint256scaledAmount);/**
* @dev An array with two elements. Each of them represents percentage from collateral
* to be taken as a commission. First element represents collateralization commission.
* Second element represents uncollateralization commission. There should be 3
* decimal buffer for each of them, e.g. 1000 = 1%.
*
* @param uint 256 index of value in array.
*/functioncommissions(uint256index)externalviewreturns(uint256);/**
* @dev 'Black hole' is any address that guarantees that tokens sent to it will not be
* retrieved from it. Note: some tokens revert on transfer to zero address.
*
* @return address address of black hole.
*/functionblackHole()externalviewreturns(address);/**
* @dev Token that will be used to harvest collected commissions.
*
* @return address address of token.
*/functioncommunityToken()externalviewreturns(address);/**
* @dev Pool of available tokens for harvesting.
*
* @param uint256 index in array.
* @return address address of token.
*/functioncommunityPool(uint256index)externalviewreturns(address);/**
* @dev Token balance available for harvesting.
*
* @param address address of token.
* @return uint256 token balance.
*/functioncommunityBalance(addresstokenAddress)externalviewreturns(uint256);/**
* @dev Array of tokens that have been dispersed.
*
* @param uint256 index in array.
* @return address address of dispersed token.
*/functiondisperseTokens(uint256index)externalviewreturns(address);/**
* @dev Amount of tokens that has been dispersed.
*
* @param address address of token.
* @return uint256 token balance.
*/functiondisperseBalance(addresstokenAddress)externalviewreturns(uint256);/**
* @dev Amount of tokens that was already taken from the disperse.
*
* @param address address of token.
* @return uint256 total amount of tokens already taken.
*/functiondisperseTotalTaken(addresstokenAddress)externalviewreturns(uint256);/**
* @dev Amount of disperse already taken by each tokenId.
*
* @param tokenId unique identifier of unit.
* @param address address of token.
* @return uint256 amount of tokens already taken.
*/functiondisperseTaken(uint256tokenId,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Mapping of `tokenId`s to token addresses that have collateralized before.
*
* @param tokenId unique identifier of unit.
* @param index in array.
* @return address address of token.
*/functioncollateralTokens(uint256tokenId,uint256index)externalviewreturns(address);/**
* @dev Token balances that are stored under `tokenId`.
*
* @param tokenId unique identifier of unit.
* @param address address of token.
* @return uint256 token balance.
*/functioncollateralBalances(uint256tokenId,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Calculator function for harvesting.
*
* @param amount of `communityToken`s to spend
* @param address address of token to be harvested
* @return amount to harvest based on inputs
*/functiongetAmount(uint256amount,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Collect commission fees gathered in exchange for `communityToken`.
*
* @param amounts[] array of amounts to collateralize
* @param address[] array of token addresses
*/functionharvest(uint256[]memoryamounts,address[]memorytokenAddresses)external;/**
* @dev Collateralize NFT with different tokens and amounts.
*
* @param tokenId unique identifier for specific NFT
* @param amounts[] array of amounts to collateralize
* @param address[] array of token addresses
*/functioncollateralize(uint256tokenId,uint256[]memoryamounts,address[]memorytokenAddresses)externalpayable;/**
* @dev Withdraw underlying collateral.
*
* Requirements:
* - only owner of NFT
*
* @param tokenId unique identifier for specific NFT
* @param amounts[] array of amounts to collateralize
* @param address[] array of token addresses
*/functionuncollateralize(uint256tokenId,uint256[]memoryamounts,address[]memorytokenAddresses)external;/**
* @dev Split collateral among all existent tokens.
*
* @param amounts[] to be dispersed among all NFT owners
* @param address[] address of token to be dispersed
*/functiondisperse(uint256[]memoryamounts,address[]memorytokenAddresses)externalpayable;}
Abstraction layer for already deployed NFT collections
interfaceIEnviousHouse{eventCollateralized(addressindexedcollection,uint256indexedtokenId,uint256amount,addresstokenAddress);eventUncollateralized(addressindexedcollection,uint256indexedtokenId,uint256amount,addresstokenAddress);eventDispersed(addressindexedcollection,addressindexedtokenAddress,uint256amount);eventHarvested(addressindexedcollection,addressindexedtokenAddress,uint256amount,uint256scaledAmount);/**
* @dev totalCollections function returns the total count of registered collections.
*
* @return uint256 number of registered collections.
*/functiontotalCollections()externalviewreturns(uint256);/**
* @dev 'Black hole' is any address that guarantees that tokens sent to it will not be
* retrieved from it. Note: some tokens revert on transfer to zero address.
*
* @param address collection address.
* @return address address of black hole.
*/functionblackHole(addresscollection)externalviewreturns(address);/**
* @dev collections function returns the collection address based on the collection index input.
*
* @param uint256 index of a registered collection.
* @return address address collection.
*/functioncollections(uint256index)externalviewreturns(address);/**
* @dev collectionIds function returns the collection index based on the collection address input.
*
* @param address collection address.
* @return uint256 collection index.
*/functioncollectionIds(addresscollection)externalviewreturns(uint256);/**
* @dev specificCollections function returns whether a particular collection follows the ERC721 standard or not.
*
* @param address collection address.
* @return bool specific collection or not.
*/functionspecificCollections(addresscollection)externalviewreturns(bool);/**
* @dev An array with two elements. Each of them represents percentage from collateral
* to be taken as a commission. First element represents collateralization commission.
* Second element represents uncollateralization commission. There should be 3
* decimal buffer for each of them, e.g. 1000 = 1%.
*
* @param address collection address.
* @param uint256 index of value in array.
* @return uint256 collected commission.
*/functioncommissions(addresscollection,uint256index)externalviewreturns(uint256);/**
* @dev Token that will be used to harvest collected commissions.
*
* @param address collection address.
* @return address address of token.
*/functioncommunityToken(addresscollection)externalviewreturns(address);/**
* @dev Pool of available tokens for harvesting.
*
* @param address collection address.
* @param uint256 index in array.
* @return address address of token.
*/functioncommunityPool(addresscollection,uint256index)externalviewreturns(address);/**
* @dev Token balance available for harvesting.
*
* @param address collection address.
* @param address address of token.
* @return uint256 token balance.
*/functioncommunityBalance(addresscollection,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Array of tokens that have been dispersed.
*
* @param address collection address.
* @param uint256 index in array.
* @return address address of dispersed token.
*/functiondisperseTokens(addresscollection,uint256index)externalviewreturns(address);/**
* @dev Amount of tokens that has been dispersed.
*
* @param address collection address.
* @param address address of token.
* @return uint256 token balance.
*/functiondisperseBalance(addresscollection,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Amount of tokens that was already taken from the disperse.
*
* @param address collection address.
* @param address address of token.
* @return uint256 total amount of tokens already taken.
*/functiondisperseTotalTaken(addresscollection,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Amount of disperse already taken by each tokenId.
*
* @param address collection address.
* @param tokenId unique identifier of unit.
* @param address address of token.
* @return uint256 amount of tokens already taken.
*/functiondisperseTaken(addresscollection,uint256tokenId,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Mapping of `tokenId`s to token addresses that have collateralized before.
*
* @param address collection address.
* @param tokenId unique identifier of unit.
* @param index in array.
* @return address address of token.
*/functioncollateralTokens(addresscollection,uint256tokenId,uint256index)externalviewreturns(address);/**
* @dev Token balances that are stored under `tokenId`.
*
* @param address collection address.
* @param tokenId unique identifier of unit.
* @param address address of token.
* @return uint256 token balance.
*/functioncollateralBalances(addresscollection,uint256tokenId,addresstokenAddress)externalviewreturns(uint256);/**
* @dev Calculator function for harvesting.
*
* @param address collection address.
* @param amount of `communityToken`s to spend.
* @param address address of token to be harvested.
* @return amount to harvest based on inputs.
*/functiongetAmount(addresscollection,uint256amount,addresstokenAddress)externalviewreturns(uint256);/**
* @dev setSpecificCollection function enables the addition of any collection that is not compatible with the ERC721 standard to the list of exceptions.
*
* @param address collection address.
*/functionsetSpecificCollection(addresscollection)external;/**
* @dev registerCollection function grants Envious functionality to any ERC721-compatible collection and streamlines
* the distribution of an initial minimum disbursement to all NFT holders.
*
* @param address collection address.
* @param address address of `communityToken`.
* @param uint256 collateralization fee, incoming / 1e5 * 100%.
* @param uint256 uncollateralization fee, incoming / 1e5 * 100%.
*/functionregisterCollection(addresscollection,addresstoken,uint256incoming,uint256outcoming)externalpayable;/**
* @dev Collect commission fees gathered in exchange for `communityToken`.
*
* @param address collection address.
* @param amounts[] array of amounts to collateralize.
* @param address[] array of token addresses.
*/functionharvest(addresscollection,uint256[]memoryamounts,address[]memorytokenAddresses)external;/**
* @dev Collateralize NFT with different tokens and amounts.
*
* @param address collection address.
* @param tokenId unique identifier for specific NFT.
* @param amounts[] array of amounts to collateralize.
* @param address[] array of token addresses.
*/functioncollateralize(addresscollection,uint256tokenId,uint256[]memoryamounts,address[]memorytokenAddresses)externalpayable;/**
* @dev Withdraw underlying collateral.
*
* Requirements:
* - only owner of NFT
*
* @param address collection address.
* @param tokenId unique identifier for specific NFT.
* @param amounts[] array of amounts to collateralize.
* @param address[] array of token addresses.
*/functionuncollateralize(addresscollection,uint256tokenId,uint256[]memoryamounts,address[]memorytokenAddresses)external;/**
* @dev Split collateral among all existent tokens.
*
* @param address collection address.
* @param amounts[] to be dispersed among all NFT owners.
* @param address[] address of token to be dispersed.
*/functiondisperse(addresscollection,uint256[]memoryamounts,address[]memorytokenAddresses)externalpayable;}
Rationale
“Envious” Term Choice
We propose adopting the term “Envious” to describe any NFT collection minted using this ERC standard or any ERC-721 based NFT collection that utilized the EnviousHouse abstraction layer.
NFT Collateralization with Multiple Tokens
Some Web3 projects primarily collateralize a specific NFT asset with one ERC-20 based token, resulting in increased gas fees and complications in User Experience (UX).
This ERC has been crafted to enable the collateralization of a designated NFT asset with multiple ERC-20 based tokens within a single transaction.
NFT Collateralization with the Native Coin
Each ERC-20 based token possesses a distinct address. However, a native coin does not carry an address. To address this, we propose utilizing a null address (0x0000000000000000000000000000000000000000) as an identifier for the native coin during collateralization, as it eliminates the possibility of collisions with smart contract addresses.
Disperse Functionality
We have implemented the capability to collateralize all assets within a particular NFT collection in a single transaction. The complete collateral amount is deposited into a smart contract, enabling each user to claim their respective share of the collateral when they add or redeem collateral for that specific asset.
Harvest Functionality
Each Envious NFT collection provides an option to incorporate a community ERC-20 based token, which can be exchanged for commissions accrued from collateralization and uncollateralization activities.
BlackHole Instance
Some ERC-20 based token implementations forbid transfers to the null address, it is necessary to have a reliable burning mechanism in the harvest transactions. blackHole smart contract removes ERC-20 communityTokens from the circulating supply in exchange for commission fees withdrawn.
blackHole has been designed to prevent the transfer of any tokens from itself and can only perform read operations. It is intended to be used with the Envious extension in implementations related to commission harvesting.
Backwards Compatibility
EnviousHouse abstraction layer is suggested for already deployed ERC-721 based NFT collections.
Security Considerations
Envious may share security concerns similar to those found in ERC-721, such as hidden logic within functions like burn, add resource, accept resource, etc.