forked from DecentralizedClimateFoundation/DCIPs
154 lines
5.4 KiB
Markdown
154 lines
5.4 KiB
Markdown
|
---
|
||
|
eip: 5298
|
||
|
title: ENS Trust to hold NFTs under ENS name
|
||
|
description: An interface for a smart contract acting as a "trust" that holds tokens by ENS name.
|
||
|
author: Zainan Victor Zhou (@xinbenlv)
|
||
|
discussions-to: https://ethereum-magicians.org/t/erc-eip-5198-ens-as-token-holder/10374
|
||
|
status: Review
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2022-07-12
|
||
|
requires: 137, 721, 1155
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This EIP standardizes an interface for smart contracts to hold of [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) 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](./eip-721.md).
|
||
|
2. Compliant contracts implement the following interface:
|
||
|
|
||
|
```solidity
|
||
|
interface IERC_ENS_TRUST is ERC721Receiver, ERC1155Receiver {
|
||
|
function claimTo(address to, bytes32 ensNode, address operator, uint256 tokenId) payable external;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
3. `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](./eip-712.md) or [EIP-1155](./eip-1155.md).
|
||
|
|
||
|
4. 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
|
||
|
|
||
|
```ts
|
||
|
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
|
||
|
|
||
|
```solidity
|
||
|
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](../LICENSE.md).
|