This standard outlines a smart contract interface eligible to represent any number of fractionalized non-fungible tokens. Existing projects utilizing standards like EIP-1633 conventionally deploy separate EIP-20 compatible token contracts to fractionalize the non-fungible token into EIP-20 tokens. In contrast, this ERC allows each token ID to represent a token type representing(fractionalizing) the non-fungible token.
This standard is approximate in terms of using _id for distinguishing token types. However, this ERC has a clear difference with EIP-1155 as each _id represents a distinct NFT.
Motivation
The conventional fractionalization process of fractionalizing a NFT to FT requires deployment of a FT token contract representing the ownership of NFT. This leads to inefficient bytecode usage on Ethereum Blockchain and limits functionalities since each token contract is separated into its own permissioned address.
With the rise of multiple NFT projects needing to fractionalize NFT to FT, new type of token standard is needed to back up them.
Specification
/**
@title Multi-Fractional Non-Fungible Token Standard
@dev Note : The ERC-165 identifier for this interface is 0x83f5d35f.
*/interfaceIMFNFT{/**
@dev This emits when ownership of any token changes by any mechanism.
The `_from` argument MUST be the address of an account/contract sending the token.
The `_to` argument MUST be the address of an account/contract receiving the token.
The `_id` argument MUST be the token type being transferred. (represents NFT)
The `_value` argument MUST be the number of tokens the holder balance is decrease by and match the recipient balance is increased by.
*/eventTransfer(addressindexed_from,addressindexed_to,uint256indexed_id,uint256_value);/**
@dev This emits when the approved address for token is changed or reaffirmed.
The `_owner` argument MUST be the address of account/contract approving to withdraw.
The `_spender` argument MUST be the address of account/contract approved to withdraw from the `_owner` balance.
The `_id` argument MUST be the token type being transferred. (represents NFT)
The `_value` argument MUST be the number of tokens the `_approved` is able to withdraw from `_owner` balance.
*/eventApproval(addressindexed_owner,addressindexed_spender,uint256indexed_id,uint256_value);/**
@dev This emits when new token type is added which represents the share of the Non-Fungible Token.
The `_parentToken` argument MUST be the address of the Non-Fungible Token contract.
The `_parentTokenId` argument MUST be the token ID of the Non-Fungible Token.
The `_id` argument MUST be the token type being added. (represents NFT)
The `_totalSupply` argument MUST be the number of total token supply of the token type.
*/eventTokenAddition(addressindexed_parentToken,uint256indexed_parentTokenId,uint256_id,uint256_totalSupply);/**
@notice Transfers `_value` amount of an `_id` from the msg.sender address to the `_to` address specified
@dev msg.sender must have sufficient balance to handle the tokens being transferred out of the account.
MUST revert if `_to` is the zero address.
MUST revert if balance of msg.sender for token `_id` is lower than the `_value` being transferred.
MUST revert on any other error.
MUST emit the `Transfer` event to reflect the balance change.
@param _to Source address
@param _id ID of the token type
@param _value Transfer amount
@return True if transfer was successful, false if not
*/functiontransfer(address_to,uint256_id,uint256_value)externalreturns(bool);/**
@notice Approves `_value` amount of an `_id` from the msg.sender to the `_spender` address specified.
@dev msg.sender must have sufficient balance to handle the tokens when the `_spender` wants to transfer the token on behalf.
MUST revert if `_spender` is the zero address.
MUST revert on any other error.
MUST emit the `Approval` event.
@param _spender Spender address(account/contract which can withdraw token on behalf of msg.sender)
@param _id ID of the token type
@param _value Approval amount
@return True if approval was successful, false if not
*/functionapprove(address_spender,uint256_id,uint256_value)externalreturns(bool);/**
@notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified.
@dev Caller must be approved to manage the tokens being transferred out of the `_from` account.
MUST revert if `_to` is the zero address.
MUST revert if balance of holder for token `_id` is lower than the `_value` sent.
MUST revert on any other error.
MUST emit `Transfer` event to reflect the balance change.
@param _from Source address
@param _to Target Address
@param _id ID of the token type
@param _value Transfer amount
@return True if transfer was successful, false if not
*/functiontransferFrom(address_from,address_to,uint256_id,uint256_value)externalreturns(bool);/**
@notice Sets the NFT as a new type token
@dev The contract itself should verify if the ownership of NFT is belongs to this contract itself with the `_parentNFTContractAddress` & `_parentNFTTokenId` before adding the token.
MUST revert if the same NFT is already registered.
MUST revert if `_parentNFTContractAddress` is address zero.
MUST revert if `_parentNFTContractAddress` is not ERC-721 compatible.
MUST revert if this contract itself is not the owner of the NFT.
MUST revert on any other error.
MUST emit `TokenAddition` event to reflect the token type addition.
@param _parentNFTContractAddress NFT contract address
@param _parentNFTTokenId NFT tokenID
@param _totalSupply Total token supply
*/functionsetParentNFT(address_parentNFTContractAddress,uint256_parentNFTTokenId,uint256_totalSupply)external;/**
@notice Get the token ID's total token supply.
@param _id ID of the token
@return The total token supply of the specified token type
*/functiontotalSupply(uint256_id)externalviewreturns(uint256);/**
@notice Get the balance of an account's tokens.
@param _owner The address of the token holder
@param _id ID of the token
@return The _owner's balance of the token type requested
*/functionbalanceOf(address_owner,uint256_id)externalviewreturns(uint256);/**
@notice Get the amount which `_spender` is still allowed to withdraw from `_owner`
@param _owner The address of the token holder
@param _spender The address approved to withdraw token on behalf of `_owner`
@param _id ID of the token
@return The amount which `_spender` is still allowed to withdraw from `_owner`
*/functionallowance(address_owner,address_spender,uint256_id)externalviewreturns(uint256);/**
@notice Get the bool value which represents whether the NFT is already registered and fractionalized by this contract.
@param _parentNFTContractAddress NFT contract address
@param _parentNFTTokenId NFT tokenID
@return The bool value representing the whether the NFT is already registered.
*/functionisRegistered(address_parentNFTContractAddress,uint256_parentNFTTokenId)externalviewreturns(bool);}interfaceERC165{/**
@notice Query if a contract implements an interface
@param interfaceID The interface identifier, as specified in ERC-165
@dev Interface identification is specified in ERC-165. This function
uses less than 30,000 gas.
@return `true` if the contract implements `interfaceID` and
`interfaceID` is not 0xffffffff, `false` otherwise
*/functionsupportsInterface(bytes4interfaceID)externalviewreturns(bool);}
To receive Non-Fungible Token on safe Transfer the contract should include onERC721Received().
Including onERC721Received() is needed to be compatible with Safe Transfer Rules.
/**
@notice Handle the receipt of an NFT
@param _operator The address which called `safeTransferFrom` function
@param _from The address which previously owned the token
@param _tokenId The NFT identifier which is being transferred
@param _data Additional data with no specified format
@return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
*/functiononERC721Received(address_operator,address_from,uint256_tokenId,bytescalldata_data)externalpurereturns(bytes4);
Rationale
Metadata
The symbol() & name() functions were not included since the majority of users can just fetch it from the originating NFT contract. Also, copying the name & symbol every time when token gets added might place a lot of redundant bytecode on the Ethereum blockchain.
However, according to the need and design of the project it could also be added to each token type by fetching the metadata from the NFT contract.
Design
Most of the decisions made around the design of this ERC were done to keep it as flexible for diverse token design & architecture.
These minimum requirement for this standard allows for each project to determine their own system for minting, governing, burning their MFNFT tokens depending on their programmable architecture.
Backwards Compatibility
To make this standard compatible with existing standards, this standard event & function names are identical with ERC-20 token standard with some more events & functions to add token type dynamically.
Also, the sequence of parameter in use of _id for distinguishing token types in functions and events are very much similar to ERC-1155 Multi-Token Standard.
Since this standard is intended to interact with the EIP-721 Non-Fungible Token Standard, it is kept purposefully agnostic to extensions beyond the standard in order to allow specific projects to design their own token usage and scenario.
Test Cases
Reference Implementation of MFNFT Token includes test cases written using hardhat. (Test coverage : 100%)
To fractionalize an already minted NFT, it is evident that ownership of NFT should be given to token contracts before fractionalization.
In the case of fractionalizing NFT, the token contract should thoroughly verify the ownership of NFT before fractionalizing it to prevent tokens from being a separate tokens with the NFT.
If an arbitrary account has the right to call setParentNFT() there might be a front-running issue. The caller of setParentNFT() might be different from the real NFT sender.
To prevent this issue, implementors should just allow admin to call, or fractionalize and receive NFT in an atomic transaction similar to flash loan(swap).