HomeEIPs
EIPsERC-5298
ERC-5298

ENS Trust to hold NFTs under ENS name

An interface for a smart contract acting as a trust that holds tokens by ENS name.
StagnantStandards Track: ERC
Created: 2022-07-12
Requires: EIP-137, EIP-721, EIP-1155
Zainan Victor Zhou (@xinbenlv)
DiscussionsOriginal linkEdit
1 min read

ERC-5298 proposes a standardized interface for smart contracts to hold EIP-721 and EIP-1155 tokens on behalf of ENS domains, decoupling NFT ownership from wallet addresses. The compliant contract must implement ERC721TokenReceiver and the IERC_ENS_TRUST interface, which includes a claimTo function that checks if the sender is the owner of the ENS node and makes a call to the safeTransferFrom function of EIP-721 or EIP-1155. Any ensNode is allowed, and the proposal is compatible with other scoped ownership namespaces.

Video
Anyone may contribute to propose contents.
Go propose
Original

Abstract

This EIP standardizes an interface for smart contracts to hold of EIP-721 and EIP-1155 tokens on behalf of ENS domains.

Motivation

Currently, if someone wants to receive a token, they have to set up a wallet address. This EIP decouples NFT ownership from wallet addresses.

Specification

  1. Compliant contracts MUST implement ERC721TokenReceiver, as defined in EIP-721.
  2. Compliant contracts implement the following interface:
interface IERC_ENS_TRUST is ERC721Receiver, ERC1155Receiver { function claimTo(address to, bytes32 ensNode, address operator, uint256 tokenId) payable external; }
  1. claimTo MUST check if msg.sender is the owner of the ENS node identified by bytes32 ensNode (and/or approved by the domain in implementation-specific ways). The compliant contract then MUST make a call to the safeTransferFrom function of EIP-721 or EIP-1155.

  2. Any ensNode is allowed.

Rationale

  1. ENS was chosen because it is a well-established scoped ownership namespace. This is nonetheless compatible with other scoped ownership namespaces.

  2. We didn't expose getters or setters for ensRoot because it is outside of the scope of this EIP.

Backwards Compatibility

No backward compatibility issues were found.

Test Cases

import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; describe("FirstENSBankAndTrust", function () { describe("Receive and Claim Token", function () { it("Should ACCEPT/REJECT claimTo based on if ENS owner is msg.sender", async function () { ... // Steps of testing: // mint to charlie // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth // bob try to claimTo alice, first time it should be rejected // bob then set the ENS record // bob claim to alice, second time it should be accepted // mint to charlie await erc721ForTesting.mint(charlie.address, fakeTokenId); // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth await erc721ForTesting.connect(charlie)["safeTransferFrom(address,address,uint256,bytes)"]( charlie.address, firstENSBankAndTrust.address, fakeTokenId, fakeReceiverENSNamehash ); // bob try to claimTo alice, first time it should be rejected await expect(firstENSBankAndTrust.connect(bob).claimTo( alice.address, fakeReceiverENSNamehash, firstENSBankAndTrust.address, fakeTokenId )) .to.be.rejectedWith("ENSTokenHolder: node not owned by sender"); // bob then set the ENS record await ensForTesting.setOwner( fakeReceiverENSNamehash, bob.address ); // bob claim to alice, second time it should be accepted await expect(firstENSBankAndTrust.connect(bob).claimTo( alice.address, fakeReceiverENSNamehash, erc721ForTesting.address, fakeTokenId )); }); }); });

Reference Implementation

pragma solidity ^0.8.9; contract FirstENSBankAndTrust is IERC721Receiver, Ownable { function getENS() public view returns (ENS) { return ENS(ensAddress); } function setENS(address newENSAddress) public onlyOwner { ensAddress = newENSAddress; } // @dev This function is called by the owner of the token to approve the transfer of the token // @param data MUST BE the ENS node of the intended token receiver this ENSHoldingServiceForNFT is holding on behalf of. function onERC721Received( address operator, address /*from*/, uint256 tokenId, bytes calldata data ) external override returns (bytes4) { require(data.length == 32, "ENSTokenHolder: last data field must be ENS node."); // --- START WARNING --- // DO NOT USE THIS IN PROD // this is just a demo purpose of using extraData for node information // In prod, you should use a struct to store the data. struct should clearly identify the data is for ENS // rather than anything else. bytes32 ensNode = bytes32(data[0:32]); // --- END OF WARNING --- addToHolding(ensNode, operator, tokenId); // conduct the book keeping return ERC721_RECEIVER_MAGICWORD; } function claimTo(address to, bytes32 ensNode, address tokenContract uint256 tokenId) public { require(getENS().owner(ensNode) == msg.sender, "ENSTokenHolder: node not owned by sender"); removeFromHolding(ensNode, tokenContract, tokenId); IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId); } }

Security Considerations

Needs discussion.

Copyright and related rights waived via CC0.

Further reading
Anyone may contribute to propose contents.
Go propose
Adopted by projects
Anyone may contribute to propose contents.
Go propose

Not miss a beat of EIPs' update?

Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.

View all
Serve Ethereum Builders, Scale the Community.
Resources
GitHub
Supported by