ERC-7743 - Multi-Owner Non-Fungible Tokens (MO-NFT)

Created 2024-07-13
Status Draft
Category ERC
Type Standards Track
Authors

Abstract

This ERC proposes a new standard for non-fungible tokens (NFTs) that supports multiple owners. The MO-NFT standard allows a single NFT to have multiple owners, reflecting the shared and distributable nature of digital assets. This model incorporates mechanisms for provider-defined transfer fees and ownership burning, enabling flexible and collaborative ownership structures. It maintains compatibility with the existing ERC-721 standard to ensure interoperability with current tools and platforms.

Motivation

Traditional NFTs enforce a single-ownership model, which does not align with the inherent duplicability and collaborative potential of digital assets. MO-NFTs allow for shared ownership, promoting wider distribution and collaboration while maintaining secure access control. The inclusion of provider fees and ownership burning enhances the utility and flexibility of NFTs in representing digital assets and services.

Specification

Data Structures

solidity mapping(uint256 => EnumerableSet.AddressSet) internal _owners;

solidity mapping(address => uint256) private _balances;

solidity mapping(uint256 => address) private _providers;

solidity mapping(uint256 => uint256) private _transferValues;

Token Creation and Ownership Model

  1. Minting:

  2. The function mintToken() allows the creation of a new MO-NFT. The caller becomes both the initial owner and the provider of the token.

    solidity function mintToken() public onlyOwner returns (uint256);

  3. A new tokenId is generated, and the caller is added to the owners set and recorded as the provider. The balanceOf the caller is incremented.

  4. Ownership List:

  5. The MO-NFT maintains a list of owners for each token. Owners are stored in an enumerable set to prevent duplicates and allow efficient lookup.

  6. Provider Role:

  7. The provider is the initial owner who can set and update the transferValue fee. Only the provider can modify certain token parameters.

  8. Transfer Mechanism:

  9. Owners can transfer the token to new owners using transferFrom. The transfer adds the new owner to the list without removing existing owners and transfers the transferValue fee to the provider.

    solidity function transferFrom(address from, address to, uint256 tokenId) public;

Transfer of Ownership

  1. Additive Ownership:

  2. Transferring ownership adds the new owner to the ownership list without removing current owners. This approach reflects the shared nature of digital assets.

  3. Ownership Tracking:

  4. The smart contract tracks the list of owners for each MO-NFT using the _owners mapping.

  5. Provider Fee Handling:

  6. During a transfer, the specified transferValue fee is transferred to the provider. The contract must have sufficient balance to cover this fee.

  7. Burning Ownership:

  8. Owners can remove themselves from the ownership list using the burn function.

    solidity function burn(uint256 tokenId) external;

Value Depreciation

  1. Value Model:

  2. As the number of owners increases, the value of the MO-NFT may decrease to reflect the reduced exclusivity. This is an optional model and can be implemented at the application level.

  3. Depreciation Mechanism:

  4. The value depreciation model can be defined based on the asset type and distribution strategy. This standard does not enforce a specific depreciation mechanism but acknowledges its importance.

Interface Definitions

Minting Functions

Transfer Functions

Ownership Management Functions

Provider Functions

Burn Function

Events

ERC-721 Compliance

The MO-NFT standard is designed to be compatible with the ERC-721 standard. It implements required functions such as balanceOf, ownerOf, and transferFrom from the IERC-721 interface.

solidity function supportsInterface(bytes4 interfaceId) public view returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC165).interfaceId; }

Rationale

  1. Multi-Ownership Model:

  2. Digital assets are inherently duplicable and can be shared without loss of quality. The multi-owner model allows broader distribution and collaboration while maintaining a unique token identity.

  3. Additive Ownership:

  4. By adding new owners without removing existing ones, we support shared ownership models common in collaborative environments and digital content distribution.

  5. Provider Fee Mechanism:

  6. Incorporating a provider fee incentivizes creators and providers by rewarding them whenever the asset is transferred. This aligns with models where creators receive royalties or fees for their work.

  7. Ownership Burning:

  8. Allowing owners to remove themselves from the ownership list provides flexibility, enabling owners to relinquish rights or convert their digital ownership into real-world assets.

  9. ERC-721 Compatibility:

  10. Maintaining compatibility with ERC-721 allows MO-NFTs to leverage existing infrastructure, tools, and platforms, facilitating adoption and interoperability.

Backwards Compatibility

While the MO-NFT standard aims to maintain compatibility with ERC-721, certain deviations are necessary due to the multi-owner model:

Developers should be aware of these differences when integrating MO-NFTs into systems designed for standard ERC-721 tokens.

Test Cases

  1. Minting an MO-NFT and Verifying Initial Ownership:

  2. Input:

    • Call mintToken() as the provider.
  3. Expected Output:

    • A new tokenId is generated.

    • The caller is added as the first owner.

    • The balanceOf the caller increases by 1.

    • The provider is recorded for the token.

    • TokenMinted event is emitted.

  4. Transferring an MO-NFT and Verifying Provider Fee Transfer:

  5. Input:

    • Call transferFrom(from, to, tokenId) where from is an existing owner and to is a new address.
  6. Expected Output:

    • The to address is added to the owners list.

    • The transferValue fee is transferred to the provider.

    • The balanceOf of the to address increases by 1.

    • TokenTransferred event is emitted.

  7. Burning Ownership:

  8. Input:

    • An owner calls burn(tokenId).
  9. Expected Output:

    • The owner is removed from the owners list.

    • The balanceOf of the owner decreases by 1.

    • If the owners list becomes empty, the token is effectively non-existent.

    • TokenBurned event is emitted.

  10. Setting Transfer Value:

  11. Input:

    • The provider calls setTransferValue(tokenId, newTransferValue).
  12. Expected Output:

    • The transferValue is updated in the contract.

    • TransferValueUpdated event is emitted.

  13. Failing Transfer to Existing Owner:

  14. Input:

    • Attempt to transferFrom to an address that is already an owner.
  15. Expected Output:

    • The transaction reverts with the error "MO-NFT: Recipient is already an owner".

    • No changes to ownership or balances occur.

Reference Implementation

The full reference implementation code for the MO-NFT standard is included in the EIPs repository under assets folder. This ensures the code is preserved alongside the EIP and remains accessible.

Key Functions in Reference Implementation

Minting Tokens

function mintToken() public onlyOwner returns (uint256) {
    _nextTokenId++;

    // Add the sender to the set of owners for the new token
    _owners[_nextTokenId].add(msg.sender);

    // Increment the balance of the owner
    _balances[msg.sender] += 1;

    // Set the provider to the caller
    _providers[_nextTokenId] = msg.sender;

    emit TokenMinted(_nextTokenId, msg.sender);
    return _nextTokenId;
}

Transferring Tokens

function transferFrom(address from, address to, uint256 tokenId) public override {
    require(isOwner(tokenId, msg.sender), "MO-NFT: Caller is not an owner");
    require(to != address(0), "MO-NFT: Transfer to zero address");
    require(!isOwner(tokenId, to), "MO-NFT: Recipient is already an owner");

    // Add the new owner to the set
    _owners[tokenId].add(to);
    _balances[to] += 1;

    // Transfer the transferValue to the provider
    uint256 transferValue = _transferValues[tokenId];
    address provider = _providers[tokenId];
    require(address(this).balance >= transferValue, "Insufficient contract balance");

    (bool success, ) = provider.call{value: transferValue}("");
    require(success, "Transfer to provider failed");

    emit TokenTransferred(tokenId, from, to);
}

Burning Ownership

function burn(uint256 tokenId) external {
    require(isOwner(tokenId, msg.sender), "MO-NFT: Caller is not an owner");

    // Remove the caller from the owners set
    _owners[tokenId].remove(msg.sender);
    _balances[msg.sender] -= 1;

    emit TokenBurned(tokenId, msg.sender);
}

Security Considerations

  1. Reentrancy Attacks:

  2. Mitigation: Use the Checks-Effects-Interactions pattern when transferring Ether (e.g., transferring transferValue to the provider).

  3. Recommendation: Consider using ReentrancyGuard from OpenZeppelin to prevent reentrant calls.

  4. Integer Overflow and Underflow:

  5. Mitigation: Solidity 0.8.x automatically checks for overflows and underflows, throwing exceptions when they occur.

  6. Access Control:

  7. Ensured By:

    • Only owners can call transfer functions.

    • Only providers can set the transferValue.

    • Use of require statements to enforce access control.

  8. Denial of Service (DoS):

  9. Consideration: Functions that iterate over owners could be expensive in terms of gas if the owners list is large.

  10. Mitigation: Avoid such functions or limit the number of owners.

  11. Data Integrity:

  12. Ensured By: Proper use of Solidity's data types and structures, and by emitting events for all state-changing operations for off-chain verification.

  13. Ether Handling:

  14. Consideration: Ensure the contract can receive Ether to handle provider payments.

  15. Mitigation: Implement a receive() function to accept Ether.

Copyright

Copyright and related rights waived via CC0.