E741 - Fungible NFTs
Abstract
This proposal introduces a hybrid token standard that combines the features of ERC-20 and ERC-721 tokens. The E741 token standard allows for fungible and non-fungible assets to be represented within a single token contract, providing enhanced functionality for applications that require both types of tokens.
Motivation
Currently, Ethereum supports two widely-used token standards: ERC-20 for fungible tokens and ERC-721 for non-fungible tokens. However, there are scenarios where applications require both fungible and non-fungible assets to be managed within the same system. For example, in decentralized games or collectibles platforms, users may hold both currency (fungible) and unique assets (non-fungible).
By combining ERC-20 and ERC-721 functionalities into a single token standard, developers can streamline their smart contract architecture and simplify interactions for users who hold both types of assets. This proposal aims to address this need by defining a unified standard for hybrid tokens on the Ethereum blockchain.
Furthermore, there are liquidity issues associated with the auction model of the ERC-721 standard and this proposal aims to address some of those limitations present in the NFT marketplace by allowing NFTâs to be sold in whole and in pieces through a Decentralized Exchange (DEX).
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.
The E741 token standard MUST inherit all functions and events from both ERC-20 and ERC-721, with additional considerations for interoperability between the two types of tokens. It is RECOMMENDED to support both types of events through an internal library, however a token MAY use another interface if it achieves the same event structure.
It is RECOMMENDED to support optional metadata features from ERC-20 and ERC-721.
The following rules MUST be followed to adhere to the E741 standard:
1.0
ERC-20 tokens represent1
NFT.- The decimals of the token combined with
1.0
represent the token id.
-i.e. 1.12345 would represent token id 12345 - The maximum total supply corresponds to the total number of NFTs sans decimal. It is OPTIONAL to either fix the total supply with the maximum NFT id, or to increase the total supply as new NFTs are minted.
- i.e. If the largest NFT id is 123, the maximum total supply is
123
.
- i.e. If the largest NFT id is 123, the maximum total supply is
- The decimals of the token MUST accomodate the number of posisble NFTs.
- i.e. Four decimals allows for max NFT id of 9999
- Specific ids can be transfered by using their ERC-20 representation
- i.e.
transfer(to, 1.12345)
would send token id 12345
- i.e.
- Sending a specifc token id will only transfer
1.0
ERC-20 tokens along side the nft.- i.e.
transfer(to, 1.12345)
would send token id 12345 +1.0
ERC-20 tokens
- i.e.
- NFTâs will be âbrokenâ during a transfer that forces a userâs balance to decrease in such a way as to cross a
1.0
ERC-20 threashold. - NFTâs will be âunbrokenâ during a transfer that forces a userâs balance to increase in such a way as to cross a
1.0
ERC-20 threshold. - NFTâs that are âbrokenâ must be maintained in a âbroken listâ temporarily.
Interface
// ERC-20 events
library libES20 {
event Transfer(address indexed from, address indexed to, uint amount);
event Approval(address indexed owner, address indexed spender, uint256 value);
function emitTransfer(address _from, address _to, uint _amount) internal { emit Transfer(_from, _to, _amount); }
function emitApproval(address _owner, address _spender, uint _value) internal { emit Approval(_owner, _spender, _value); }
}
// ERC-721 events
library libES721 {
event Transfer(address indexed _from, address indexed _to, uint indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function emitTransfer(address _from, address _to, uint _tokenId) internal { emit Transfer(_from, _to, _tokenId); }
function emitApproval(address _owner, address _approve, uint _tokenId) internal { emit Approval(_owner, _approve, _tokenId); }
function emitApprovalForAll(address _owner, address _operator, bool _approved) internal { emit ApprovalForAll(_owner, _operator, _approved); }
}
interface IERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
interface IERC-20 {
function balanceOf(address account) external view returns (uint256);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function decimals() external view returns (uint);
}
interface IERC-721 is IERC165 {
function balanceOf(address account) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
// payable removed for ERC-20 etherscan compatibility
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
interface IE741 is IERC-20, IERC-721 {
// supportsInterface 0x5a46575f
// library transfers can not be included in the interface
// incorporate them directly with library
function balanceOf(address account) external override(IERC-20, IERC-721) view returns (uint256);
function approve(address spender, uint256 value) external override(IERC-20, IERC-721) returns (bool);
function transferFrom(address from, address to, uint256 value) external override(IERC-20, IERC-721) returns (bool);
}
interface IERC-721Metadata {
function name() external view returns (string memory _name);
function symbol() external view returns (string memory _symbol);
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
interface IERC-7572 {
function contractURI() external view returns (string memory);
event ContractURIUpdated();
}
interface IERC-20Metadata {
function name() external view returns (string memory _name);
function symbol() external view returns (string memory _symbol);
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
Rationale
There are many limitations to the ERC-721 standard, namely the problem of liquidity. The majority of NFTs on the open market leave owners without the ability to liquidate assets despite a high floor price. In the worst case this leaves people with serious tax implications if they hold an NFT with a high floor price and no interested buyers in their particular NFT.
Besides the liquidity issue, partial ownership of NFTâs is not possible, and a range of possibilities opens with the paradigm of partial NFT ownersip. This not only gives someone the ability to slowly accumulate enough to cross the threshold into NFT ownership but it also allows someone to temporarilty liquidate a portion of their NFT asset for a short period of time.
With this proposal we hope to open the world to new possibilities with NFTs to bring them into the free market DEX environment.
Backwards Compatibility
The creators of E741 worked hard to maintain backwards compatibility with the ERC-20 and ERC-721 standard, however one feature was not possible to adhere to.
The ERC-721 standard has the payable
keyword on several functions that are non payable
in ERC-20. In order to support both standards generally the payable
keyword was removed from the incompatible functions.
In our experience it is rare for a protocol to force payment through approve
, transferFrom
, and safeTransferFrom
. However, if a protocol does force payment, the transactions will fail with the E741 standard. We found this to be an acceptible compromise compared to the benefits of the standard.
In order to bring the marketplace towards the E741 standard, the creators have also developed wrappers to bridge the gap from ERC-20 to E741, and from ERC-721 to E741. Any ERC-20 can be ânft-izedâ and any ERC-721 can be âtoken-izedâ.
Test Cases
Comprehensive unit test cases were performed on the first implementation of E741, and the creators will strive to open source test cases for otherâs to ensure their tokens meet the standards of this proposal.
Security Considerations
Existing protocols may be succeptible to loss of funds if precautions are not taking when handling E741 tokens. Specifically when a value such as 1.12345
is used to transfer 1.0
ERC-20 worth of tokens along side an NFT with id 12345, the protocol may internally track a deposit/withdraw of 1.12345
resulting in mismanaged accounting.
Implementations
https://etherscan.io/token/0x382EDfe4c6168858C81893fE00fCB7b68914d929#code