214 lines
12 KiB
Markdown
214 lines
12 KiB
Markdown
---
|
|
eip: 4675
|
|
title: Multi-Fractional Non-Fungible Tokens
|
|
description: Fractionalize multiple NFTs using a single contract
|
|
author: David Kim (@powerstream3604)
|
|
discussions-to: https://ethereum-magicians.org/t/eip-4675-multi-fractional-non-fungible-token-standard/8008
|
|
status: Draft
|
|
type: Standards Track
|
|
category: ERC
|
|
created: 2022-01-13
|
|
requires: 165, 721
|
|
---
|
|
|
|
## Abstract
|
|
This standard outlines a smart contract interface eligible to represent any number of fractionalized non-fungible tokens. Existing projects utilizing standards like [EIP-1633](./eip-1633.md) conventionally deploy separate [EIP-20](./eip-20.md) 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](./eip-1155.md) 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
|
|
|
|
```solidity
|
|
/**
|
|
@title Multi-Fractional Non-Fungible Token Standard
|
|
@dev Note : The ERC-165 identifier for this interface is 0x83f5d35f.
|
|
*/
|
|
interface IMFNFT {
|
|
/**
|
|
@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.
|
|
*/
|
|
event Transfer(address indexed _from, address indexed _to, uint256 indexed _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.
|
|
*/
|
|
event Approval(address indexed _owner, address indexed _spender, uint256 indexed _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.
|
|
*/
|
|
event TokenAddition(address indexed _parentToken, uint256 indexed _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
|
|
*/
|
|
function transfer(address _to, uint256 _id, uint256 _value) external returns (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
|
|
*/
|
|
function approve(address _spender, uint256 _id, uint256 _value) external returns (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
|
|
|
|
*/
|
|
function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external returns (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
|
|
*/
|
|
function setParentNFT(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
|
|
*/
|
|
function totalSupply(uint256 _id) external view returns (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
|
|
*/
|
|
function balanceOf(address _owner, uint256 _id) external view returns (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`
|
|
*/
|
|
function allowance(address _owner, address _spender, uint256 _id) external view returns (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.
|
|
*/
|
|
function isRegistered(address _parentNFTContractAddress, uint256 _parentNFTTokenId) external view returns (bool);
|
|
}
|
|
|
|
interface ERC165 {
|
|
/**
|
|
@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
|
|
*/
|
|
function supportsInterface(bytes4 interfaceID) external view returns (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.
|
|
```solidity
|
|
/**
|
|
@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)"))`
|
|
*/
|
|
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external pure returns (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%)
|
|
|
|
## Reference Implementation
|
|
[MFNFT - Implementation](../assets/eip-4675/README.md)
|
|
|
|
## Security Considerations
|
|
|
|
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).
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|