Fractionally Represented Non-Fungible Token
Abstract
This proposal introduces a standard for fractionally represented non-fungible tokens, allowing NFTs to be managed and owned fractionally within a single contract. This approach enables NFTs to coexist with an underlying fungible representation seamlessly, enhancing liquidity and access without dividing the NFT itself, or requiring an explicit conversion step. The standard includes mechanisms for both fractional and whole token transfers, approvals, and event emissions. This specification draws from design in both ERC-721 and ERC-20, but is not fully compatible with either standard.
Motivation
Fractional ownership of NFTs has historically relied on external protocols that manage division and reconstitution of individual NFTs into fractional representations. The approach of dividing specific NFTs results in fragmented liquidity of the total token supply, as the fractional representations of two NFTs are not equivalent and therefore must be traded separately. Additionally, this approach requires locking of fractionalized NFTs, preventing free transfer until they are reconstituted.
This standard offers a unified solution to fractional ownership, aiming to increase the liquidity and accessibility of NFTs without compromising transferability or flexibility.
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.
Fractionally Represented Non-Fungible Token Interface
All ERC-7651 compliant contracts MUST implement the ERC-7651 and ERC-165 interfaces.
Compliant contracts MUST emit fractional Approval or Transfer events on approval or transfer of tokens in fractional representation.
Compliant contracts MUST additionally emit non-fungible ApprovalForAll, Approval or Transfer on approval for all, approval, and transfer in non-fungible representation.
Note that this interface draws from similarly defined functions in the ERC-721 and ERC-20 standards, but is not fully backwards compatible with either.
interface IERC7651 is IERC165 {
/// @dev This emits when fractional representation approval for a given spender
/// is changed or reaffirmed.
event FractionalApproval(address indexed owner, address indexed spender, uint256 value);
/// @dev This emits when ownership of fractionally represented tokens changes
/// by any mechanism. This event emits when tokens are both created and destroyed,
/// ie. when from and to are assigned to the zero address respectively.
event FractionalTransfer(address indexed from, address indexed to, uint256 amount);
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can manage all NFTs of the owner.
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
/// @dev This emits when the approved spender is changed or reaffirmed for a given NFT.
/// A zero address emitted as spender implies that no addresses are approved for
/// this token.
event NonFungibleApproval(
address indexed owner,
address indexed spender,
uint256 indexed id
);
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are both created and destroyed, ie. when
/// from and to are assigned to the zero address respectively.
event NonFungibleTransfer(address indexed from, address indexed to, uint256 indexed id);
/// @notice Decimal places in fractional representation
/// @dev Decimals are used as a means of determining when balances or amounts
/// contain whole or purely fractional components
/// @return Number of decimal places used in fractional representation
function decimals() external view returns (uint8 decimals);
/// @notice The total supply of a token in fractional representation
/// @dev The total supply of NFTs may be recovered by computing
/// `totalSupply() / 10 ** decimals()`
/// @return Total supply of the token in fractional representation
function totalSupply() external view returns (uint256 totalSupply);
/// @notice Balance of a given address in fractional representation
/// @dev The total supply of NFTs may be recovered by computing
/// `totalSupply() / 10 ** decimals()`
/// @param owner_ The address that owns the tokens
/// @return Balance of a given address in fractional representation
function balanceOf(address owner_) external view returns (uint256 balance);
/// @notice Query if an address is an authorized operator for another address
/// @param owner_ The address that owns the NFTs
/// @param operator_ The address being checked for approval to act on behalf of the owner
/// @return True if `operator_` is an approved operator for `owner_`, false otherwise
function isApprovedForAll(
address owner_,
address operator_
) external view returns (bool isApproved);
/// @notice Query the allowed amount an address can spend for another address
/// @param owner_ The address that owns tokens in fractional representation
/// @param spender_ The address being checked for allowance to spend on behalf of the owner
/// @return The amount of tokens `spender_` is approved to spend on behalf of `owner_`
function allowance(
address owner_,
address spender_
) external view returns (uint256 allowance);
/// @notice Query the owner of a specific NFT.
/// @dev Tokens owned by the zero address are considered invalid and should revert on
/// ownership query.
/// @param id_ The unique identifier for an NFT.
/// @return The address of the token's owner.
function ownerOf(uint256 id_) external view returns (address owner);
/// @notice Set approval for an address to spend a fractional amount,
/// or to spend a specific NFT.
/// @dev There must be no overlap between valid ids and fractional values.
/// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner if an id is provided.
/// @dev Throws if the id is not a valid NFT
/// @param spender_ The spender of a given token or value.
/// @param amountOrId_ A fractional value or id to approve.
/// @return Whether the approval operation was successful or not.
function approve(
address spender_,
uint256 amountOrId_
) external returns (bool success);
/// @notice Set approval for a third party to manage all of the callers
/// non-fungible assets
/// @param operator_ Address to add to the callers authorized operator set
/// @param approved_ True if the operator is approved, false if not approved
function setApprovalForAll(address operator_, bool approved_) external;
/// @notice Transfer fractional tokens or an NFT from one address to another
/// @dev There must be no overlap between valid ids and fractional values
/// @dev The operation should revert if the caller is not `from_` or is not approved
/// to spent the tokens or NFT owned by `from_`
/// @dev The operation should revert if value is less than the balance of `from_` or
/// if the NFT is not owned by `from_`
/// @dev Throws if the id is not a valid NFT
/// @param from_ The address to transfer fractional tokens or an NFT from
/// @param to_ The address to transfer fractional tokens or an NFT to
/// @param amountOrId_ The fractional value or a distinct NFT id to transfer
/// @return True if the operation was successful
function transferFrom(
address from_,
address to_,
uint256 amountOrId_
) external returns (bool success);
/// @notice Transfer fractional tokens from one address to another
/// @dev The operation should revert if amount is less than the balance of `from_`
/// @param to_ The address to transfer fractional tokens to
/// @param amount_ The fractional value to transfer
/// @return True if the operation was successful
function transfer(address to_, uint256 amount_) external returns (bool success);
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT
/// @dev Throws if `from_` is not the current owner
/// @dev Throws if `to_` is the zero address
/// @dev Throws if `tokenId_` is not a valid NFT
/// @dev When transfer is complete, this function checks if `to_` is a
/// smart contract (code size > 0). If so, it calls `onERC721Received`
/// on `to_` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`.
/// @param from_ The address to transfer the NFT from
/// @param to_ The address to transfer the NFT to
/// @param tokenId_ The NFT to transfer
/// @param data_ Additional data with no specified format, sent in call to `to_`
function safeTransferFrom(
address from_,
address to_,
uint256 id_,
bytes calldata data_
) external;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This is identical to the above function safeTransferFrom interface
/// though must pass empty bytes as data to `to_`
/// @param from_ The address to transfer the NFT from
/// @param to_ The address to transfer the NFT to
/// @param tokenId_ The NFT to transfer
function safeTransferFrom(address from_, address to_, uint256 id_) external;
}
interface IERC165 {
/// @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);
}
Fractionally Represented Non-Fungible Token Metadata Interface
This is a RECOMMENDED interface, identical in definition to the ERC-721 Metadata Interface. Rather than using this interface directly, a distinct metadata interface should be used here to avoid confusion surrounding ERC-721 inheritance. Given function definitions here are identical, it’s important to note that the ERC-165 interfaceId
will be identical between metadata interfaces for this specification and that of ERC-721.
/// @title ERC-7651 Fractional Non-Fungible Token Standard, optional metadata extension
interface IERC7651Metadata {
/// @notice A descriptive, long-form name for a given token collection
function name() external view returns (string memory name);
/// @notice An abbreviated, short-form name for a given token collection
function symbol() external view returns (string memory symbol);
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `tokenId_` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "ERC721
/// Metadata JSON Schema".
/// @param id_ The NFT to fetch a token URI for
/// @return The token's URI as a string
function tokenURI(uint256 id_) external view returns (string memory uri);
}
Fractionally Represented Non-Fungible Token Banking Interface
This is a RECOMMENDED interface that is intended to be used by implementations of ERC-7651 that implement NFT ID reuse.
interface IERC7651NFTBanking {
/// @notice Get the number of NFTs that have been minted but are not currently owned.
/// @dev This should be the number of unowned NFTs, limited by the total
/// fractional supply.
/// @return The number of NFTs not currently owned.
function getBankedNFTsLength() external view returns (uint256 bankedNFTsLength);
/// @notice Get a paginated list of NFTs that have been minted but are not currently owned.
/// @param start_ Start index in bank.
/// @param count_ Number of tokens to return from start index, inclusive.
/// @return An array of banked NFTs from `start_`, of maximum length `count_`.
function getBankedNFTs(
uint256 start_,
uint256 count_
) external view returns (uint256[] memory bankedNFTs);
/// @notice Query the current supply of NFTs in circulation.
/// @dev Given supply may remain banked or unminted, this function should always be
/// inclusively upper-bounded by `totalSupply() / 10 ** decimals()`.
/// @return The current supply of minted NFTs
function totalNonFungibleSupply() external view returns (unit256);
}
Fractionally Represented Non-Fungible Token Transfer Exemptable Interface
This is a RECOMMENDED interface that is intended to be used by implementations of ERC-7651 that want to allow users to opt-out of NFT transfers.
interface IERC7651NFTTransferExemptable {
/// @notice Returns whether an address is NFT transfer exempt.
/// @param account_ The address to check.
/// @return Whether the address is NFT transfer exempt.
isNFTTransferExempt(address account_) external view returns (bool);
/// @notice Allows an address to set themselves as NFT transfer exempt.
/// @param isExempt_ The flag, true being exempt and false being non-exempt.
setSelfNFTTransferExempt(bool isExempt_) external;
}
Rationale
This standard unifies the representation of fractional ownership with the non-fungible token model, aligning closely with ERC-721 principles while enabling the functionality of ERC-20 transfers. This dual compatibility aims to mitigate the integration complexity for existing protocols. Our goal is to implicitly support as high a degree of backwards compatibility with ERC-20 and ERC-721 standards as possible to reduce or negate integration lift for existing protocols. The core rationale for this fractional NFT standard centers on two main strategies: first, designing interfaces that clearly align with either ERC-721 or ERC-20 standards to avoid ambiguity; and second, detailing implementation approaches that distinctly separate the logic of overlapping functionalities.
ID & Amount Isolation
Ensuring clear differentiation between token IDs and fractional amounts is central to this design. This non-overlapping design principle means that no input should be ambiguously interpreted as both an ID and an amount. We won’t dive into implementation guidelines, but implementations may achieve this through various means, such as validating ownership for ID inputs or reserving specific ranges for token IDs.
This approach ensures that logic in “overlapping” interfaces is similarly isolated, such that the chance of an unexpected outcome is minimized.
Events
The overlap of event signatures between the ERC-20 and ERC-721 standards presents a challenge for backward compatibility in our fractional NFT standard. Various approaches have been explored, including aligning with a single standard’s events or introducing unique events with distinct parameter indexing to resolve conflicts.
We feel that when moving towards standardization, ensuring events are properly descriptive and isolated is the ideal solution despite introducing complexity for indexing software. As a result, we adhere to traditional transfer and approval event definitions, though distinguish these events by the Fractional
or NonFungible
prefixes.
Transfers
In a standard ERC-7651 transfer, value can be transferred by specifying either a fractional amount or a specific NFT ID.
NFT ID Transfers: Transferring by NFT ID is straightforward. The specified NFT, along with its entire associated fractional value (equivalent to 10 ** decimals()), is transferred from the sender to the recipient.
Fractional Amount Transfers: Transferring fractional amounts introduces complexity in managing NFT allocations. There are three main scenarios:
- No change in whole token balance: If the transfer does not change the overall balance of either party, NFT allocations remain unchanged.
- Sender’s whole token balance decreases: If the sender’s overall balance decreases below the nearest whole number, a proportionate number of NFTs must be removed from their holdings.
- Receiver’s whole token balance increases: Conversely, if the receiver’s overall balance increases above the nearest whole number, their NFT holdings must be proportionately increased.
While ERC-7651 provides a broad framework for fractional NFTs, it does not prescribe specific methods for handling these scenarios. Common practices include monotonically minting or burning tokens to reflect changes, or tracking NFT ownership with a stack or queue during transfers of fractional amounts.
NFT Transfer Exemption
Transferring fractional amounts means that a large number of NFTs can be moved in a single transaction, which can be costly in gas usage. We recommend an optional opt-in mechanism for exemption from NFT transfers that both EOAs and contracts can use to reduce the gas burden of transferring large token amounts when the NFT representation is not needed.
When executing the function call to either opt-in or opt-out of NFT transfers, NFTs held by the address will be directionally rebalanced to ensure they stay in sync with the new exemption status. In other words, when opting-out of NFT transfers, an address’s NFTs will be banked and their NFT balance set to 0. When opting-in to NFT transfers, sufficient NFTs will be pulled from the bank and transferred to the address to match their fractional token balance.
NFT Banking
As discussed in the Transfers section, when an address newly gains a full token in fractional terms, they are consequently owed an NFT. Similarly, when an address drops below a full token in fractional terms an NFT must be removed from their balance to stay in sync with their fractional balance.
The NFT banking mechanism provides a space in which un-owned but available NFTs relative to supply are tracked. We remain unopinionated on implementation here, but want to provide a handful of examples that would fit specification.
One approach to reconcile the bank is by monotonically burning and minting NFT IDs as they are pulled from and added back to circulation, respectively. The minting portion of this strategy can incur significant gas costs that are generally not made up for by the slight gas refund of deleting storage space for burnt token IDs. This approach additionally introduces inflexibility for collections that desire a persistent, finite ID space.
An alternate implementation of ERC-7651 includes a mechanism to store and reuse IDs rather than repeatedly burning and minting them. This saves significant gas costs, and has the added benefit of providing a predictable and externally readable stream of token IDs that can be held in a queue, stack or other data structure for later reuse. The specific data structure used for this banking mechanism is immaterial and is left at the discretion of any implementations adhering to the standard.
ERC-165 Interface
We include the ERC-165 interface in specification both to adhere to ERC-721 design philosophy, and as a means of exposing interfaces at the contract level. We see this as a valuable, accepted standard to adhere to such that integrating applications may identify underlying specification.
Note that ERC-7651 contracts should not make any claim through supportsInterface
to support ERC-721 or ERC-20 standards as, despite strong backwards compatibility efforts, these contracts cannot fully adhere to existing specifications.
Metadata
In-line with ERC-721, we’ve decided to isolate replicated metadata functionality through a separate interface. This interface includes traditional naming and token URI logic, though also introduces patterns surrounding token banking visibility, as outlined above in both the NFT Banking and Transfer Logic sections.
Backwards Compatibility
The fractional non-fungible token standard aims to be nearly backwards compatible with existing ERC-721 and ERC-20 standards, though makes no claim to fully adhere to either and has as such been proposed through a distinct interface.
Events
Events in ERC-721 and ERC-20 specifications share conflicting signatures on approval and transfer, meaning an adherent hybrid of the two cannot be achieved.
This is one of the few areas where backwards compatibility has been intentionally broken, resulting in a new series of events with either a Fractional
or NonFungible
prefix. We believe that a decisive move to a non-conflicting, descriptive solution is ideal here, though will require external lift for indexing software.
balanceOf
The balanceOf
function as defined in both ERC-20 and ERC-721 standards varies, in practice, to represent either fractional or whole token ownership respectively. Given fractional non-fungible tokens should adhere to an underlying fractional representation, it follows that this function should return a balance in that representation. This does, however, imply that fractional NFT contracts cannot fully adhere to the balanceOf
specification provided by ERC-721.
Success Return Values
The transfer
and approve
functions both return a boolean value indicating success or failure. This is non-standard for the ERC-721 specification, though is standard for ERC-20. Fractional non-fungible tokens adhere to a returned boolean value to meet minimum expectations for the ERC-20 standard, acknowledging that this deviates from a state of ideal backwards compatibility.
Security Considerations
Interface Misinterpretation
This section is placeholder for further discussion surrounding the misidentification of ERC-7651 as being either ERC-20 or ERC-721. Namely, discussion surrounding potential security implications of interface misinterpretation need to be thoroughly considered.
Needs discussion.
Copyright
Copyright and related rights waived via CC0.