This proposal introduces an attribute of ownership and profit share quantities for each token under an NFT. This attribute signifies a stake in the ownership and profit rights associated with the NFT’s specific privileges, enabling the querying, transferring, and approval of these shares, thereby making the shares represented by each token applicable in a broader range of use cases.
Motivation
At times, when we wish to distribute dividends or assign rights to tokens of an NFT based on their share of ownership, it becomes necessary to equip each token with an attribute indicating the number of ownership shares. While ERC-1155 allows for the representation of ownership stakes through the balance of a token held by a wallet address, it sacrifices the uniqueness of each token. Conversely, ERC-721 maintains the uniqueness of each token but lacks an attribute to signify the share of ownership rights, and its metadata does not allow for the free transfer of these share quantities by the token owner. This extension seeks to merge the features of ERC-1155 and ERC-721, enabling holders of each share to possess characteristics akin to those of a token owner, thus bridging the gap between share representation and token uniqueness.
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.
Implementers of this extension MUST have all of the following functions:
pragmasolidity^0.8.0;interfaceIERC7628/* is IERC721 */{/// @notice Returns the number of decimal places used for ownership shares.
/// @return The number of decimal places for ownership shares.
functionshareDecimals()externalviewreturns(uint8);/// @notice Returns the total sum of ownership shares in existence for all tokens.
/// @return The total sum of ownership shares.
functiontotalShares()externalviewreturns(uint256);/// @notice Returns the ownership share of the specified token.
/// @param tokenId The identifier of the token.
/// @return The ownership share of the token.
functionshareOf(uint256tokenId)externalviewreturns(uint256);/// @notice Returns the share allowance granted to the specified spender by the owner for the specified token.
/// @param tokenId The identifier of the token.
/// @param spender The address of the spender.
/// @return The share allowance granted to the spender.
functionshareAllowance(uint256tokenId,addressspender)externalviewreturns(uint256);/// @notice Approves the specified address to spend a specified amount of shares on behalf of the caller.
/// @param tokenId The identifier of the token.
/// @param spender The address of the spender.
/// @param shares The amount of shares to approve.
functionapproveShare(uint256tokenId,addressspender,uint256shares)external;/// @notice Transfers ownership shares from one token to another.
/// @param fromTokenId The identifier of the sender token.
/// @param toTokenId The identifier of the recipient token.
/// @param shares The amount of shares to transfer.
functiontransferShares(uint256fromTokenId,uint256toTokenId,uint256shares)external;/// @notice Transfers ownership shares from one token to another address (resulting in a new token or increased shares at the recipient address).
/// @param fromTokenId The identifier of the sender token.
/// @param to The address of the recipient.
/// @param shares The amount of shares to transfer.
functiontransferSharesToAddress(uint256fromTokenId,addressto,uint256shares)external;/// @notice Adds a specified amount of shares to a token, only callable by the contract owner.
/// @param tokenId The identifier of the token.
/// @param shares The amount of shares to add.
functionaddSharesToToken(uint256tokenId,uint256shares)external;/// @notice Emitted when ownership shares are transferred from one token to another.
/// @param fromTokenId The identifier of the sender token.
/// @param toTokenId The identifier of the recipient token.
/// @param amount The amount of shares transferred.
eventSharesTransfered(uint256indexedfromTokenId,uint256indexedtoTokenId,uint256amount);/// @notice Emitted when an approval is granted for a spender to spend shares on behalf of an owner.
/// @param tokenId The token identifier.
/// @param spender The address of the spender.
/// @param amount The amount of shares approved.
eventSharesApproved(uint256indexedtokenId,addressindexedspender,uint256amount);}
Rationale
Share Issuance to a Token
Issuing additional shares to a token allows for flexible management of ownership stakes in digital assets, catering to the evolving needs of stakeholders. It ensures transparency and security in modifying ownership structures directly on the blockchain, facilitating scenarios like profit sharing or investment adjustments.
Transferring Shares to an Address
Enabling shares to be transferred to an address enhances NFT liquidity and accessibility by allowing fractional ownership. This feature supports diverse use cases like fractional sales or collateralization, making NFTs more adaptable and inclusive for a broader audience.
pragmasolidity^0.8.0;import"@openzeppelin/contracts/token/ERC721/ERC721.sol";import"@openzeppelin/contracts/access/Ownable.sol";import"@openzeppelin/contracts/security/ReentrancyGuard.sol";contractERC7628isIERC7628,ERC721,Ownable,ReentrancyGuard{mapping(uint256=>uint256)private_shareBalances;mapping(uint256=>mapping(address=>uint256))private_shareAllowances;uint256private_totalShares;uint256private_nextTokenId;constructor(addressinitialOwner)ERC721("MyToken","MTK")Ownable(initialOwner){}functionaddSharesToToken(uint256tokenId,uint256shares)publicoverrideonlyOwner{require(tokenId>0,"ERC7628: tokenId cannot be zero");_shareBalances[tokenId]+=shares;_totalShares+=shares;emitSharesTransfered(0,tokenId,shares);}functionshareDecimals()externalpureoverridereturns(uint8){return18;}functiontotalShares()externalviewoverridereturns(uint256){return_totalShares;}functionshareOf(uint256tokenId)externalviewoverridereturns(uint256){return_shareBalances[tokenId];}functionshareAllowance(uint256tokenId,addressspender)externalviewoverridereturns(uint256){return_shareAllowances[tokenId][spender];}functionapproveShare(uint256tokenId,addressspender,uint256shares)externaloverride{require(spender!=ownerOf(tokenId),"ERC7628: approval to current owner");require(msg.sender==ownerOf(tokenId),"ERC7628: approve caller is not owner");_shareAllowances[tokenId][spender]=shares;emitSharesApproved(tokenId,spender,shares);}functiontransferShares(uint256fromTokenId,uint256toTokenId,uint256shares)externaloverridenonReentrant{require(_shareBalances[fromTokenId]>=shares,"ERC7628: insufficient shares for transfer");require(_isApprovedOrOwner(msg.sender,fromTokenId),"ERC7628: transfer caller is not owner nor approved");_shareBalances[fromTokenId]-=shares;_shareBalances[toTokenId]+=shares;emitSharesTransfered(fromTokenId,toTokenId,shares);}functiontransferSharesToAddress(uint256fromTokenId,addressto,uint256shares)externaloverridenonReentrant{require(_shareBalances[fromTokenId]>=shares,"ERC7628: insufficient shares for transfer");require(_isApprovedOrOwner(msg.sender,fromTokenId),"ERC7628: transfer caller is not owner nor approved");_nextTokenId++;_safeMint(to,_nextTokenId);_shareBalances[_nextTokenId]=shares;emitSharesTransfered(fromTokenId,_nextTokenId,shares);}// Helper function to check if an address is the owner or approved
function_isApprovedOrOwner(addressspender,uint256tokenId)internalviewreturns(bool){return(spender==ownerOf(tokenId)||getApproved(tokenId)==spender||isApprovedForAll(ownerOf(tokenId),spender));}}
Security Considerations
Clear Approvals on Transfer
When transferring token ownership, it is crucial to clear all existing approvals. This precaution prevents previously authorized parties from retaining access after the token has changed hands.
Prevent Reentrancy
Implementations must guard against reentrancy attacks. This involves ensuring that functions altering balances or ownership are secure against such vulnerabilities, particularly during share transfers.
Validate IDs and Addresses
Verifying the legitimacy of token IDs and wallet addresses in all operations is essential. This step helps avoid errors and ensures that tokens and their associated shares are handled correctly.
Manage Shares on Ownership Change
Proper management of share quantities is vital during a token ownership transfer. It’s important to ensure that shares are accurately accounted for and transferred alongside the token to maintain the integrity of ownership stakes.