⚠️ This EIP is not recommended for general use or implementation as it is likely to change.

EIP-4972: Name-Owned Account Source

Name-Owned Account for Social Identity

AuthorQi Zhou
Discussions-Tohttps://ethereum-magicians.org/t/eip-4972-name-owned-account/8822
StatusDraft
TypeStandards Track
CategoryERC
Created2022-04-04
Requires 20, 721

Abstract

This ERC proposes a new type of account - name-owned account (NOA) that is controlled by the owner of the name besides existing externally-owned account (EOA) and contract account (CA). With the new account type, users will be able to transfer/receive tokens using the name-derived address directly instead of the address of the name owner. A NOA can be as a social identity with all states on-chain even under 3rd-party or self custody. It also simplifies porting the social identity from one custody to another.

Motivation

A popular way to onboard Web2 users to the Web3 world is custody. However, current custody models have severe drawbacks. Considering the following widely adopted custody models:

  1. The custodian uses one EOA/CA to hold the assets of all users. This is not compatible with on-chain social protocols since all user activities are off-chain.
  2. One EOA per user. The social identity is not portable, which means there is no way for users to migrate their social identity across different custody platforms.
  3. One CA (e.g. Gnosis Safe) per user. The one time deployment cost is super high and the user experience is not good.

To solve all these problems, this ERC proposes a new type of account - name-owned account (NOA). Using NOA as social identity instead of EOA/CA brings huge benefits for users:

  • Easy Web2 user onboarding. We are providing standard Web2 user experiences with human readable names and 3rd-party custody. Every user of a centralized platform can immediately have a NOA by using the username as the name of NOA custodied by the platform.
  • On-chain states. All user states are on-chain even under custody so it’s 100% compatible with social protocols.
  • Portable Account. A NOA can be easily ported to different custody platforms by changing the owner.
  • Flexible Account Management. We can use one EOA/CA to control any number of NOAs.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Name-Owned Account

An NOA has

  1. a name for social identity; and
  2. an address derived from the name to receive tokens; and
  3. owner(s) of the name that can transfer the token.

The name should be human-readable and can be easily recognized socially. An example is the username of a centralized platform such as FB, Twitter. The name-derived address (NDA) is a normal Ethereum address that should not collide with the existing addresses of EOA/CA. Since we cannot use NDA as msg.sender, the right to transfer the tokens of the NDA is controlled by the owner/owners of the name. The name to owner/owners mapping is managed by an on-chain name service, and the owner/owners are EOA/CA, which can be the addresses of 3-rd custodians (e.g. FB) or self-custodian. By changing the owner of the name to the EOA of the user (can be done by requesting the custodian), the NDA becomes self-custodian, and no one should be able to transfer the assets unless the approved by the self-custodian user.

Name Representation

A name is represented by a bytes array which is ABI encoded. It MAY contain metadata of the name such as the name service the name belongs to. Examples of the name are “vitalik.eth”, “[email protected]”, or “qizhou.fb”.

Interface

INameOwnedAccount

interface INameOwnedAccount {
    /// @notice This function resolves the _name to its derived address
    /// @dev The implementation SHOULD avoid collision between name 
    /// derived address and EOA/CA
    function addressOfName(bytes memory _name) public view returns(address);

    /// @notice This function returns true if and only if the operator is the owner of the _name
    /// @dev The ownership MAY be defined by a name service such as ENS
    function isNameOwner(bytes memory _from, address operator) public view returns(bool);
}

IERC721NOA

interface IERC721NOA is IERC721, INameOwnedAccount  {
    /// @notice Transfers the ownership of an NFT from a name to an address
    /// @dev Throws unless `msg.sender` is the owner of _from. Throw if _from is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. 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,address,uint256,bytes)"))`.
    function safeTransferFromName(bytes memory _from, address _to, uint256 _tokenId, bytes _data) public returns(bool);

   /// @notice Transfers the ownership of an NFT from a name to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to "".
    function safeTransferFromName(bytes memory _from, address _to, uint256 _tokenId) public returns(bool);

    /// @notice Change or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    ///  Throws unless `msg.sender` is the owner of _owner. Throw if _owner is not
    ///  the current owner.
    function approveFromName(bytes memory _owner, address _operator, uint256 _tokenId) public returns(bool);

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all of _owner’s assets
    /// @dev Throws unless `msg.sender` is the owner of _owner. Throw if _owner is not
    ///  the current owner. Emits the ApprovalForAll event. The contract MUST allow
    ///  multiple operators per owner.
    function setApprovalForAllFromName(bytes memory _owner, address _operator, bool _approved) public returns(bool);
    
    /// @notice This function returns true if interfaceId is the id of IERC721NOA
    /// @dev see {IERC165-supportsInterface}
    function supportsInterface(bytes4 interfaceId) external view returns(bool);
}

IERC20NOA

interface IERC20NOA is IERC20, INameOwnedAccount {
    /// @notice Transfers _value amount of tokens from name  _from to address _to, 
    /// @dev Throws unless `msg.sender` is the owner of _owner. Throw if _owner is not
    ///  the current owner. Throw if the balance of _from does not have enough tokens to 
    ///  spend. Emits the Transfer event.  
    function transferFromName(bytes memory _from, address _to, uint256 _value) public returns(bool);

    /// @notice Allows _spender to withdraw from _owner multiple times, up to
    ///  the _value amount.
    ///  @dev Throws unless `msg.sender` is the owner of _owner. Throw if _owner is 
    ///  not the current owner. If this function is called again it overwrites the current
    ///  allowance with _value.
    function approveFromName(bytes memory _owner, address _spender, uint256 _value) public returns(bool);
    
    /// @notice This function returns true if interfaceId is the id of IERC721NOA
    /// @dev see {IERC165-supportsInterface}
    function supportsInterface(bytes4 interfaceId) external view returns(bool);
}

Authentication

The transfer and approve function is authenticated if and only if the message sender is the owner of the name.

Rationale

We use bytes array to represent a name to ensure it’s flexible enough to deal with different use cases. E.g. one can encode the name service contract address the name belongs to into the bytes array. One can also encode extra authentication data, such as zero knowledge proofs, into the bytes array. In the future, we may propose a standard to formalize the name for wider adoption.

The isNameOwner function is sufficient for authenticating the message sender. One can verify the owner by looking up the name owner from a name service, or check zero knowledge proofs encoded in name to prove the ownership directly without looking up anything.

The addressOfName interface decouples the implementation from specific hashing algorithms, as long as the generated address doesn’t collide with EOA/CA address space.

Backwards Compatibility

The new account type is compatible with existing ERC token standards.

Reference Implementation

Name Format

The decoded format of bytes name is not defined at this standard. One straightforward implementation would be:

bytes memory name = abi.encode((string, ‘address’), (username, nameService))

where the username is the string representation of the username and nameService is the name service contract address. This will decouple the implementation from specific name services such as ENS.

Name Derived Address (INameOwnedAccount.addressOfName())

With the bytes format mentioned above, we can follow the similar rule of CREATE2 opcode to compute the NOA address from nameService and hash of the username as address(keccak256(0xff, keccak256(“eip-4972.addressOfName”), nameService, keccak256(username))). This can ensure it won’t collide with existing smart contract account addresses.

Ownership of a Name (INameOwnedAccount.isNameOwner())

Normally we can get the owner from the name service and compare it with the message sender. We recommend the name service to define an owner function in the same format as ENS.

Security Considerations

No security considerations were found.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Qi Zhou, "EIP-4972: Name-Owned Account [DRAFT]," Ethereum Improvement Proposals, no. 4972, April 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4972.