ENS Trust to hold NFTs under ENS name
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.