ENS Trust to hold NFTs under ENS name
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
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
- Compliant contracts MUST implement
ERC721TokenReceiver
, as defined in EIP-721. - 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; }
-
claimTo
MUST check ifmsg.sender
is the owner of the ENS node identified bybytes32 ensNode
(and/or approved by the domain in implementation-specific ways). The compliant contract then MUST make a call to thesafeTransferFrom
function of EIP-721 or EIP-1155. -
Any
ensNode
is allowed.
Rationale
-
ENS was chosen because it is a well-established scoped ownership namespace. This is nonetheless compatible with other scoped ownership namespaces.
-
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
Copyright and related rights waived via CC0.
Adopted by projects
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