ERC-7628 ERC-721 ownership share extension (ERC-721 Ownership Shares Extension)
Review · NFTs & multi-token · created 2024-02-20
An extension standard that attaches an ownership/profit-share quantity to each token of an ERC-721 NFT, allowing that share to be queried, transferred to other tokens or addresses, and approved.
Why it is needed
To split dividends or allocate rights proportionally to shares on an NFT, each token needs an attribute representing an ownership share quantity. However, this is difficult with existing standards. ERC-1155, which handles multiple token types as quantities, can represent shares through the token balance held by a wallet address, but doing so requires giving up the uniqueness of each individual token. Conversely, ERC-721, which represents unique tokens, preserves the uniqueness of each token but has no attribute representing ownership share, and metadata alone does not let the token owner freely move the share quantity. This extension combines the characteristics of both standards so that each shareholder has properties similar to a token owner, bridging the gap between representing shares and token uniqueness.
What to implement
function shareDecimals() external view returns (uint8);: Returns the number of decimal places used for shares.function totalShares() external view returns (uint256);: Returns the total sum of shares existing across all tokens.function shareOf(uint256 tokenId) external view returns (uint256);: Returns the share quantity held by the specified token.function shareAllowance(uint256 tokenId, address spender) external view returns (uint256);: Returns the share limit that the token owner has allowed the spender address.function approveShare(uint256 tokenId, address spender, uint256 shares) external;: Approves spender to use shares up to a specified quantity on behalf of the caller.function transferShares(uint256 fromTokenId, uint256 toTokenId, uint256 shares) external;: Moves shares from one token to another.function transferSharesToAddress(uint256 fromTokenId, address to, uint256 shares) external;: Moves a token's shares to an address. The receiving address either gets a new token or has its existing share increased.function addSharesToToken(uint256 tokenId, uint256 shares) external;: Adds shares to a token. Can only be called by the contract owner.event SharesTransfered(uint256 indexed fromTokenId, uint256 indexed toTokenId, uint256 amount);: Emitted when shares are moved from one token to another.event SharesApproved(uint256 indexed tokenId, address indexed spender, uint256 amount);: Emitted when approval is granted for shares to be spent on behalf of the owner.
In depth
Share query interface
There are four view functions for querying shares. shareDecimals returns the number of decimal places used for shares, and totalShares returns the total sum of shares existing across all tokens. shareOf(tokenId) returns the share quantity held by a specific token, and shareAllowance(tokenId, spender) returns the share limit that the token owner has allowed the spender address. Contracts implementing this extension MUST have all functions listed in the specification. In the reference implementation, shareDecimals returns 18.
Share approval and the two transfer methods
approveShare(tokenId, spender, shares) approves spender to use shares up to a specified quantity on behalf of the caller. There are two transfer functions. transferShares(fromTokenId, toTokenId, shares) moves shares from one token to another. transferSharesToAddress(fromTokenId, to, shares) moves a token's shares to an address, either creating a new token for the recipient or increasing their existing share. The source text explains that transferring shares to an address enables partial ownership, increasing the liquidity and accessibility of the NFT and supporting use cases such as partial sales or collateralization.
Additional share issuance: addSharesToToken
addSharesToToken(tokenId, shares) is a function that adds shares to a specific token, and can only be called by the contract owner. The source text explains that additional issuance allows ownership structure to be changed transparently and securely on-chain to match changing stakeholder needs, assisting scenarios such as profit distribution or investment adjustment. In the reference implementation, the onlyOwner restriction is applied, tokenId is checked to be nonzero, the token's share and _totalShares are increased together, and a SharesTransfered event is emitted with fromTokenId set to 0.
Events and the permission checks in the reference implementation
There are two events. SharesTransfered(fromTokenId, toTokenId, amount) is emitted when shares are moved between tokens, and SharesApproved(tokenId, spender, amount) is emitted when approval to use shares is granted. The event name is spelled exactly as in the source text, SharesTransfered. The reference implementation's approveShare checks that the caller is the token owner and prevents the current owner from approving themselves. Both transfer functions use _isApprovedOrOwner to check that the caller is either the owner or an address approved via getApproved or isApprovedForAll, and use the nonReentrant modifier to prevent reentrancy. transferSharesToAddress increments _nextTokenId, then creates a new token via _safeMint and assigns the shares to it.
Compatibility and proposal status
This standard requires ERC-721 (requires: 721), and the source text's Backwards Compatibility section states it is fully ERC-721 compatible. The behavior of existing wallets or apps that handle ERC-721 remains unchanged, with share functionality layered on as additional functions and events. Meanwhile, this proposal's status is Review, meaning it is still under review and its interface or details may change. When implementing or integrating, it is safer to check the latest version of the source text.
Things to watch out for
- Clearing approvals on ownership transfer: When token ownership is transferred, all existing approvals must be cleared. This prevents a previously approved address from retaining access after the token's owner has changed.
- Reentrancy attack prevention: Implementations must prevent reentrancy attacks. In particular, functions that change balances or ownership, such as share transfers, must be made safe against this vulnerability.
- Token ID and address validation: All operations must verify that the token ID and wallet address are valid. This avoids errors and ensures the token and its associated shares are processed correctly.
- Managing shares on ownership change: When token ownership is transferred, share quantities must be managed properly. Shares must be calculated and transferred accurately together with the token to keep the ownership structure intact.