Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC-2571: Creators’ Royalty Token standard #2571

Closed
mogya2 opened this issue Mar 29, 2020 · 16 comments
Closed

ERC-2571: Creators’ Royalty Token standard #2571

mogya2 opened this issue Mar 29, 2020 · 16 comments
Labels

Comments

@mogya2
Copy link

mogya2 commented Mar 29, 2020

---
eip: 2571
title: Creators’ Royalty Token standard
author: Nao Hanamura(@naomasabit),
       Shoji Fukunaga(@mogya2) <s.fukunaga@conata.world>,
       Wataru Shinohara(@wshino),
       Shumpei Koike(@shunp), 
       Akira Tsuruoka(@akira-19)
discussions-to: https://github.com/ethereum/EIPs/issues/2571
status: Draft
type: Standards Track
category: ERC
created: 2020-03-29
---

Simple Summary

A standard interface for non-fungible tokens that enables artwork creators to receive a fee not only when their works are sold for the first time, but also their works are resold.

Abstract

This standard outlines a smart contract interface based on ERC-721, which is a standard for non-fungible tokens. The key issue of using it is that an original author who creates an item cannot receive any fees after giving or selling it to another. Creators' Royalty Token enables creators to get a fee whenever the token is transferred, which has a function of the decentralized exchange. Hence, a partial amount of the transaction value will always be paid to the creator.

Motivation

It is predicted that a lot of creators who design a piece of artwork would associate their items with non-fungible tokens based on ERC-721. The expected issue in this case is that the artwork would be resold on the secondary market, such as OpenSea, even though the artist cannot get any fees. This problem often happens even in the real world, and that makes creators disappointed.
The new functionality is possible with the design of receiving a fee for the sale whenever non-fungible tokens are transferred. You do not need to embed any code but use this interface instead of ERC-721 so that artwork creators can receive a fee. Currently, we are developing a product called Conata and will implement the token based on this standard.​

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.

Smart contracts implementing the ERC-2571 standard MUST implement all of the functions in the ERC-2571 interface.

Smart contracts implementing the ERC-2571 standard MUST implement the ERC-721.

contract IERC2571 is ERC721Full, ERC721Mintable, Ownable {
​
    event ReadyForSale(
        address buyer,
        uint256 tokenId,
        uint256 price,
        uint256 expireBlockNumber);
​
    function getPublisherFeeRate() public view returns (uint256);
​
    function getCreator(uint256 tokenId) public view returns (address payable);
​
    function getCreatorFeeRate(uint256 tokenId) public view returns (uint256);
​
    function getTradeExpires(bytes32 tradeHash) public view returns (uint256);
​
    function getTradePrices(bytes32 tradeHash) public view returns (uint256);
​
    function setPublisherFeeRate(uint256 feeRate) external;
​
    function setCreator(uint256 tokenId, address payable creator) public;
​
    function setCreatorFeeRate(uint256 tokenId, uint256 feeRate) external;
​
    function readyForSale(address buyer, uint256 tokenId, uint256 price, uint256 expireBlockNumber) external;
​
    function cancelTradeOrder(address buyer, uint256 tokenId) external;
​
    function tradeAndDistributeFees(address payable seller, uint256 tokenId) external payable;
}
​

The token contract MUST implement the above contract. The implementation MUST follow the specifications described below.​

Creators' Royalty Token Overview
Screenshot 2020-04-01 02 26 09

Creators' Royalty Token

Buyer
Everyone can buy non-fungible tokens via Creators' Royalty Token contract. The buyer pays ETH as the token price. Paid ETH is distributed to Publisher and Creator as a trade fee.

Seller
The owner of a non-fungible token can sell it via Creators' Royalty Token contract. The seller sends the non-fungible token to a buyer.

Creator
Creators produce artworks such as characters, music and digital arts. Their artworks are published as non-fungible tokens.

Publisher
Publishers mint a Creators’ token instead of the creator. The role of them is to advertise artworks and enhance their brand images. The Creators' address should be set if there is no publisher. In this case, creators manage their token themselves.

View Functions

The view functions detailed below MUST be implemented.

getPublisherFeeRate function

function getPublisherFeeRate() public view returns (uint256)

Get the fee rate used on a publisher.
returns: Fee rate of the publisher

getCreator function

function getCreator(uint256 tokenId) public view returns (address payable)

Get the creators' address.
params: tokenId: tokenID used on ERC721
returns: Address of the creator

getCreatorFeeRate function

function getCreatorFeeRate(uint32 tokenId) public view returns (uint256)

Get the fee rate according to the tokenId.
params: tokenIdassetType: tokenID used on ERC721
returns: Fee rate of the asset type

NOTE: If the contract has a large number of tokenIDs, you can optionally set the classification as an asset type by token digits and link the classification to the creator instead of using the tokenID. In that case, the contract has to have a state valuable that plays a role as a filter.

NOTE: If the contract has a large number of tokenIDs, you can optionally set the classification as an asset type by token digits and link the classification to the creator instead of using the tokenID. In that case, the contract has to have a state valuable that plays a role as a filter.

  • The buyer argument MUST be the address of an account/contract that wants to buy the asset (is not the same as msg.sender, which is the publisher’s address)
  • The price argument MUST be more than zero.
  • The expireBlockNumber MUST be between more than zero and less than the number of the current block.
  • The keccak256 hash, which is calculated by msg.sender, buyer and tokenId, is stored as state value to use in the TradeAndDistributionFees function.

(implementation)

function readyForSale(
   address buyer,
   uint256 tokenId,
   uint256 price,
   uint256 expireBlockNumber) external {
        require(buyer != address(0), "Buyer doesn't exist");
        require(price > 0, "Price argument must be more than zero");
        require(expireBlockNumber > 0, "ExpireBlockNumber must be more than zero");
        require(expireBlockNumber > block.number, "ExpireBlockNumber is expired");
        bytes32 tradeHash = keccak256(abi.encodePacked(msg.sender, buyer, tokenId));
        _setTradeExpires(tradeHash, expireBlockNumber);
        _setTradePrices(tradeHash, price);
        TradeOrders[tradeHash]._isCancelled = false;
        emit ReadyForSale(buyer, tokenId, price, expireBlockNumber);
    }

NOTE: This function emits the event named ReadyForSale.

Trade and Distribution Fees

function tradeAndDistributeFees(address payable seller, uint256 tokenId) external payable

Call this function to transfer the ownership of the artwork and distribute fees to the author. In this function, the two functions are invoked; _signAndPayTransfer and _changeTransfer.

  • The seller argument MUST be the address of an account/contract that wants to sell the asset.
  • The block number, when being called, MUST NOT exceed the limit data that can be fetched by the getTradeExpires function. -- - The block number is associated with a hash value, which is created by the seller address, the msg.sender address and tokenId.
  • The msg.value MUST be more than the trade price, which is set through the _setTradePrices function.
  • The hash value, which is calculated from the seller, msg.sender and tokenId, is used for checking whether or not it matches the value that is created in the ReadyForSale function.
  • _tradeAndDistributeFees is a private function that distributes fees, which are computed in this function, to the creator, and the publisher. The code below is the implementation example.
function _tradeAndDistributeFees(address payable seller, uint256 tokenId, uint256 price) internal {
        uint256 creatorFeeRate = getCreatorFeeRate(tokenId);
        uint256 creatorFee = _computeCreatorFee(price, creatorFeeRate);
        uint256 publisherFee = _computePublisherFee(price);
        uint256 payment = uint256(price.sub(creatorFee).sub(publisherFee));
​
        address payable creator = getCreator(tokenId);
​
        _transferFrom(seller, msg.sender, tokenId);
        creator.transfer(creatorFee);
        _publisher.transfer(publisherFee);
        seller.transfer(payment);
 }
​

_changeTransfer is a private function that returns ETH to the sender, which is the remaining number of the transaction. The code below is the implementation example.

function _changeTransfer(uint256 tradePrice) internal {
        if (msg.value > tradePrice) {
            uint256 change = msg.value.sub(tradePrice);
            msg.sender.transfer(change);
        }
}

NOTE: The Transfer event, which is defined on the ERC-721, is emitted by this function.

Example) Creators' Royalty Tokens related sequence flow on Conata Project.

conata (1)

Cancel TradeOrder

function cancelTradeOrder(address buyer, uint256 tokenId) external

Call this function so that the buyer could cancel the order of the trade.

  • The buyer argument MUST be the address of an account/contract that ordered the request for buying the token ID.
  • The tokenId argument MUST be the number that the buyer has chosen.

Setter Function

function setPublisherFeeRate(uint256 feeRate) external

The feeRate argument CAN be set as a publisher fee rate by the owner of the contract.

function setCreator(uint256 tokenId, address payable creator) public

The creator argument MUST be the address of an account that created the artwork. The owner of the contract CAN map the tokenId and creator address.

function setCreatorFeeRate(uint256 tokenId, uint256 feeRate) external

The feeRate argument CAN be set as a creator fee rate by the owner of the contract.

Rationale

Based on ERC721

ERC-721 is widely applied to many projects that use non-fungible tokens such as CryptoKitties. Since a creators' artwork is registered as non-fungible tokens, this standard inherits ERC-721.

Each artwork has a unique token ID. Also, the token will be transferred through ERC-721 _transferFrom function in tradeAndDistributeFee function, though there are some restrictions to transfer tokens as explained in the following section.

Limited Transfer

This standard doesn’t provide a general transfer function, by which non-fungible tokens holders can transfer their non-fungible tokens freely whenever they want. This is because creators can’t get royalties if it is possible. Transfer is executed with tradeAndDistributeFees function, and every time holders transfer their non-fungible tokens, the creators can get royalties through the function.

Decentralized Exchange Function

This standard provides DEX through readyForSale function and tradeAndDistributeFee function. In order to allow a buyer to purchase a non-fungible token, the token’s holder (seller) passes a buyer’s address and a token ID as arguments to readyForSale function, and stores the hash value of the arguments with the seller’s own address. After that, the buyer calls tradeAndDistributeFee, which checks the hash value, and transfers the NFT and distributes the fees if the hash value is correct.​

Fee Distribution

In this standard, not only creators but also publishers can get some fees. Furthermore, publishers can set the fee rate like OpenSea, which is one of the most famous non-fungible token secondary markets. A fee rate for publishers is set through setPublisherFeeRate and A fee rate for creators is set through setCreatorFeeRate.

Copyright

Copyright and related rights waived via CC0.

@mogya2 mogya2 closed this as completed Mar 29, 2020
@mogya2 mogya2 changed the title noop Creators' Royalty Token standard Mar 31, 2020
@mogya2 mogya2 reopened this Mar 31, 2020
@mogya2 mogya2 changed the title Creators' Royalty Token standard ERC-2571: Creators’ Royalty Token standard Mar 31, 2020
@insiderq
Copy link

insiderq commented Apr 6, 2020

Hey there,
It's Alex from Rarible, we are working on implementing on-chain royalties on the platform right now. We'd like to participate in this EIP and adopt it if it's suitable for our platform. We also are working closely with OpenSea.
So i have several questions regarding the current proposal:
1 - why there should be an additional call for making the token available for sale? From my. perspective it's enough to have the fee rate stated in a contract so that every platform can obtain this information and distribute royalties accordingly.
2 - Can author change this fee rate after deployment? It would be unfair for the buyer who already purchased it.
3 - We have a single collection for all of out authors and we want to let them choose their royalty fee level independently. How about we introduce fee rate on the token level that overrides fee level of a collection?

@akira-19
Copy link

akira-19 commented Apr 10, 2020

Hi @insiderq,
It's Akira, one of the authors.
Thank you for your great questions !
Here is the answers.

  1. Because, without the function, someone could buy a token from the token owner even if the owner doesn’t want to sell it.
  2. Your concern is right. If the fee rate is raised after someone purchased the token, it is more difficult to sell the token for buyers than the time they bought. So it is better to put limits like creators and publishers can’t raise the original fee rate.
  3. Either a contract owner or a token owner could have authority to change the rate. In our case, a platform can set a fee rate in each token. What do you mean in a single collection? Can you give us an example?

@Nokhal
Copy link

Nokhal commented May 21, 2020

Hello.

I really like where you guys are coming from, you are absolutely right when it come to the fact that promising second-hand remuneration to creators is what could make them switch from old world licensed creation to NFT backed creation.

However, I feel like most of your functionalities listed could be left at the token publisher discretion instead of being part of the Standard.

A Standard goal is to promote inter-operability by describing interaction channels and shared predicate/postulate contracts for each of those channels, not to describe what must happen "inside the box" of each implementation.

  • You are suggesting that 11 (!) extra functions must be implemented in order to follow this standard.
    None of them have clear predicate nor postulate. I feel what is being described is an implementation of the standard, not the standard itself.
    e.g : Why is setCreatorFeeRate listed if it is meant to not be called by parties outside of the publisher/creator ecosystem ?

  • If a token do not need some of those functions (for example, no creators, only a publisher), should these functions still be implemented ? What are the throw conditions ? What are the consequences on ERC-165 signature ?

  • This standard does not allow gasless listing and auctionning -unless i'm missing something-, which is a core feature of marketplaces such as OpenSea.

Do you feel that following the following standard would prevent you guys from implementing any of your desired features ?
#2665

All of those can be implemented under the hood of the proposed getTransferFee function

function getPublisherFeeRate() public view returns (uint256);
function getCreator(uint256 tokenId) public view returns (address payable);
function getCreatorFeeRate(uint256 tokenId) public view returns (uint256);
function getTradeExpires(bytes32 tradeHash) public view returns (uint256);
function getTradePrices(bytes32 tradeHash) public view returns (uint256);
​function setPublisherFeeRate(uint256 feeRate) external;
function setCreator(uint256 tokenId, address payable creator) public;
function setCreatorFeeRate(uint256 tokenId, uint256 feeRate) external;`

And the rest be implemented under the throw conditions and payable mutability of the Transfer/Approve functions, which are already part of the ERC-721 standard.

event ReadyForSale(
        address buyer,
        uint256 tokenId,
        uint256 price,
        uint256 expireBlockNumber);
​
function readyForSale(address buyer, uint256 tokenId, uint256 price, uint256 expireBlockNumber) external;
function cancelTradeOrder(address buyer, uint256 tokenId) external;
function tradeAndDistributeFees(address payable seller, uint256 tokenId) external payable;

@naomasabit
Copy link

@Nokhal
Thank you for your comment. It is right that there are some ambiguous points as a "standard" as your points, so it should be provided reference implementation later. In addition, it should be considered to implement the compatibility with ERC165 and remove publisher-related functions from MUST.

Do you feel that following the following standard would prevent you guys from implementing any of your desired features ?

This standard is to distribute fees as a percentage of the NFT sale, not as a fixed fee under this 2571 standard. It's pretty hard to know this in existing DEXs because we have to use price oracle or get them to support this to get sales price. That's why, we designed 2571 to be tightly coupled with DEX functions, unlike 2665.

@wildlifechorus
Copy link

First of all. Great job! Are you guys still working on this? Are there any competing standards besides 2665?

@netsparkle
Copy link

Within this standard, can an argument be used to define a minimum fee in order to prevent a sale for a minimal amount which would circumvent a fee, while still allowing for percentage where the percentage would be more than the minimum?

@claytantor
Copy link

Looove this standard. Is there any reason I can't just implement for my DAPP it and hope for the best?

@LongTheSnake
Copy link

You are free to go, it's CC0 licensed, « You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. »
You might wanna take into account some comments from Discussion for ERC-721 Royalties EIP(the whole thread is nice reading😍) such as « Consider renaming creator to royaltyRecipient in the code for clarity. Royalty recipient should probably be transferable in most cases, and so creator isn't really accurate. »

@ethereum ethereum deleted a comment from wastedheadshop Apr 11, 2021
@ethereum ethereum deleted a comment from wastedheadshop Apr 11, 2021
@NightRabbit
Copy link

Is this post still active?

@MorphBC
Copy link

MorphBC commented May 7, 2021

It seems Rarible and OpenSea implemented their own Royalty fees and activity on this EIP vanished

@MyEtherAuthority
Copy link

MyEtherAuthority commented May 15, 2021

Hi, anyone has code sample implementation of ERC2571 with ERC721 ?

Thanks,

@mogya2
Copy link
Author

mogya2 commented May 15, 2021

We are currently implementing a sample project.
We are planning to update the specification because some parts are not in accordance with the current NFT specification from the beginning.

@andrewschreiber
Copy link

Looks like the Royalty standard for 721s and 1155s will be EIP-2981. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2981.md

@PhABC
Copy link
Contributor

PhABC commented Sep 20, 2021

It will indeed be difficult for 2571 to gain adoption compared to 2981 unless 2571 is drastically simplified imo.

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Mar 19, 2022
@github-actions
Copy link

github-actions bot commented Apr 2, 2022

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

15 participants