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

EIP-2981: ERC-721 Royalty standard - Standardized means of accepting royalties for NFT marketplaces across the ecosystem #2981

Merged
merged 14 commits into from
Sep 22, 2020
187 changes: 187 additions & 0 deletions EIPS/EIP-1776.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
eip: 1776
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
title: ERC-721 Royalty Standard
author: Zach Burks (@vexycats)
discussions-to: https://github.com/ethereum/EIPs/issues/2907
status: Draft
type: Standards Track
category: ERC
created: 2020-09-15
requires: 721
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
---

## Simple Summary

A standardized way to handle royalty payments for ERC-721 tokens, including publicly viewable information and notification of payment event.

## Abstract

Many of the largest ERC-721 marketplaces have implemented a form of royalties that is not compatible across the board and therefore worthless if the NFT is sold on another marketplace, defeating the purpose of any royalty system. This standard is a proposed way to minimally implement royalties that can be accepted across any type of NFT marketplace smart contracts. This standard allows for a royalty standard to be accepted on all marketplaces - leaving the funds transfer up to the marketplace itself, and only providing a means to fetch the royalty amounts and a event to be fired off when transfer has happened.
VexyCats marked this conversation as resolved.
Show resolved Hide resolved

## Motivation

This extension provides even more flexibility to the [ERC-721 specification](./eip-721.md). It is possible to set a royalty amount that can be paid to the creator on any marketplace that implements this ERC. If a marketplace chooses not to implement this ERC then of course no funds are paid for secondary sales. But seeing as how most NFT marketplace have developed some sort of royalty system themselves - and all of them are singular and only work on their own contracts - there needs to be an accepted standard way of providing royalties - if the creator so chooses to set royalties on their NFTs.
Many decentralized marketplaces use different means to facilitate the trade of NFTs - therefore this ERC does not propose a method of transferring funds - but it simply provides a means to return the royalty amounts, and a function to be called once the transfer has been completed. Allowing each marketplace to easily integrate the standard in whatever flow they currently use for their marketplace.
VexyCats marked this conversation as resolved.
Show resolved Hide resolved


## 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.

**ERC-721 compliant contracts MAY implement this ERC for royalties to provide a standard method of accepting royalty payments and receiving royalty information**

The `_RoyaltyAmount` **MUST** be calculated as a percentage - such as "5" - for 5%. This is **REQUIRED** to maintain uniformity across the standard.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limits minimum royalties to 1%. Recommend having a much lower minimum. One option is to have two fields, one for the numerator and one for the denominator. Another option is to just have this be a fixed point percentage (in the range 0 to 1) with a scaling factor of 10^18. A final option would be to have this be fixed point with a scaling factor of 10000 or something.

Note: Currently, this is value is a multiplier on the amount that is a fixed point value with scaling factor of 100.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I choose fixed point with scaling to 100 as all the other marketplaces with royalties do not let you go below 1%, with the average royalty percentage being 1-10%.

I'm not opposed to making it go lower. I'm not sure which would be best, a scaling factor of 10,000, or scaled to 10^18.

Most users would not need/select a percentage of 0.00005% though - as an NFT purchase is a onetime transaction and not like a general fee on all transactions on something like say uniswap.

Continuing to go through these and replying. Thank you for the feedback Michah!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used 10,000 as the scaling factor as I don't see any reason someone would want to go below that for an NFT royalty fee.

Im unsure if its a good move, as for adopting the standard a marketplace really needs to make sure its math is correct and it uses the 10000 scale factor, otherwise it might have a bad time..... I'd like an easier solution where it requires less math/attention to detail for implementation, but alas I don't know of what would be better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of 10^18 is that it is very standard across Ethereum, so it is more likely that tools will "just work" with that then with some custom scaling factor. The advantage of 100 is that it is easy to just describe the value as a "percentage". Personally, I would just do 10^18 I think because I believe that 100 is way too limiting (I can imagine people wanting sub percentage royalties) and I think standardization is valuable. You aren't really saving anything by using 10,000 as all numbers in Solidity use the same number of bytes of storage (unless you are bit packing, which I don't think you are or should).


Marketplaces that implement the `Royalty` standard **MAY** implement any method of calculation or transfer of funds depending on their marketplace style and smart contract code.
VexyCats marked this conversation as resolved.
Show resolved Hide resolved

```solidity
pragma solidity ^0.6.0;
import "./ERC165.sol";

/**
* @dev Implementation of royalties for 721s
*
*/
abstract contract Royalties is ERC165 {
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
abstract contract Royalties is ERC165 {
interface Royalties is ERC165 {

By making this an interface, it will force you to write an interface specification rather than an implementation (see other comments throughout for details on this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thank you. As mentioned above it will be converted to a contract as this was my implementation and interface probably shouldn't be included for the standard

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be converted to a contract as this was my implementation and interface probably shouldn't be included for the standard

Do you mean the other way around? Interface standards should never have implementation details in the specification, though sometimes a reference implementation may be provided in the ## Implementation section.

/*
* ERC165 bytes to add to interface array - set in parent contract implementing this standard
*
* bytes4(keccak256('royaltyInfo()')) == 0x46e80720
* bytes4 private constant _INTERFACE_ID_ERC721ROYALTIES = 0x46e80720;
* _registerInterface(_INTERFACE_ID_ERC721ROYALTIES);
*/

uint256 private royalty_amount;
address private creator;
bytes4 private constant _INTERFACE_ID_ERC721ROYALTIES = 0x46e80720;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private variables are implementation details and not part of an interface specification. They should not be included in the specification. It may be valuable to include a reference implementation down in the ## Implementations section if desired.

Copy link
Contributor Author

@VexyCats VexyCats Sep 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will adjust thanks.

/**
@notice This event is emitted when royalties are transferred.

@dev The marketplace would emit this event from their contracts. Or they would call royaltiesReceived() function.

@param creator The original creator of the NFT entitled to the royalties
@param buyer The person buying the NFT on a secondary sale
@param amount The amount being paid to the creator
*/
event ReceivedRoyalties(
address indexed creator,
address indexed buyer,
uint256 indexed amount
);
VexyCats marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice Constructor called from the NFT being deployed with the value for the royalty in percentage and the creator who will receive the royalty payment
*
* @param _amount The percentage value on each sale that will be transferred to the creator
* @param _creator The original creator of the NFT entitled to the royalties
*
*/

constructor(uint256 _amount, address _creator) internal {
royalty_amount = _amount;
creator = _creator;
_registerInterface(_INTERFACE_ID_ERC721ROYALTIES);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @notice Constructor called from the NFT being deployed with the value for the royalty in percentage and the creator who will receive the royalty payment
*
* @param _amount The percentage value on each sale that will be transferred to the creator
* @param _creator The original creator of the NFT entitled to the royalties
*
*/
constructor(uint256 _amount, address _creator) internal {
royalty_amount = _amount;
creator = _creator;
_registerInterface(_INTERFACE_ID_ERC721ROYALTIES);
}

Constructors are an implementation detail and should not be part of the specification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger. Will adjust. This is my code of implementation - will remove this and clean it up to remove implementation leaving only specifics for the standard.

/**
* @notice Called to return the royalty amount that was set and only return that amount
*/
function royaltyAmount() public view returns (uint256) {
return royalty_amount;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function royaltyAmount() public view returns (uint256) {
return royalty_amount;
}
function royaltyAmount() external view returns (uint256);

Interface Specifications don't specify implementation details, only public interface definitions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the tokenId missing from this? Is the idea that all NFTs that are part of a set have the same royalty amount?

Copy link
Contributor Author

@VexyCats VexyCats Sep 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - I considered weighing the royalty per NFT - and there is a few pros and a few cons.

Overall the main reason to go towards a royalty per contract (all the NFTs in this one contract) was due to gas costs during minting.

Setting a royalty value per NFT works - but prohibits the ability to mint multiple NFTs in a single transaction - by increasing the state per NFT. I've redone the 721 standard for batch minting and can achieve upwards of 2,500 NFTs in a single transaction, but if royalties are set per NFT, it would be less than 40 per transaction, a factor of 25 less. Same goes for setting a value like URI instead of using a chronological URI pattern and baseURI. So when making this standard I was focused on ensuring that gas costs remain as low as possible.

I see the value prop of having a royalty per NFT, but there are downsides for the creator. If the creator wanted to set royalties for all their NFTs - in the long run of minting each NFT they would be paying more than if they were able to set the base royalty amount.

Thinking about it further - you could handle it the same way as a baseURI and (this is purely implementation) return the base royalty amount for all NFTs, or individual amount if its set.

Therefore the function could take a tokenId argument, and the returned value only needs to be a uint256, regardless if its a baseURI or not.

Okay - I've convinced myself lol. Will adjust changed and allow for tokenId input. Makes sense as it offers more flexibility, albeit costing more gas during implementations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think whether royalties are contract wide or not is an implementation detail. One could imagine a token contract where all tokens have the same royalty, in which case no matter what tokenID you pass in you get the same result, but updating it merely requires updating a single variable. Another token may have a default fee that can be overridden by individual tokens, and a third may have a fee rate per token.


/**
* @notice Called to return both the creator's address and the royalty percentage - this would be the main function called by marketplaces unless they specifically need just the royaltyAmount
*/
function royaltyInfo() external view returns (uint256, address) {
return (royalty_amount, creator);
}
VexyCats marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice Called to verify if contract implements royalties - OPTIONAL as supportsInterface() can be called as well.
* @param _creator The original creator of the NFT entitled to the royalties
* @param _buyer The buyer of the NFT in a secondary sale
* @param _amount The amount paid for royalties on this secondary sale. (Price of ERC721 sold * Royalty Percentage)
*/
function royaltiesReceived(
address _creator,
address _buyer,
uint256 _amount
) external {
emit ReceivedRoyalties(_creator, _buyer, _amount);
}
VexyCats marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice Called to verify if contract implements royalties - OPTIONAL as supportsInterface() can be called as well.
*/
function hasRoyalties() public pure returns (bool) {
return true;
}
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
}



```

### Examples

The `Royalty` ERC being used on a ERC-721 during deployment:

**Deploying an ERC-721 and setting the royalty amount and creator**

```
constructor (string memory name, string memory symbol, string memory baseURI) public Royalties(royalty_amount, msg.sender){
_name = name;
_symbol = symbol;
_setBaseURI(baseURI);
// register the supported interfaces to conform to ERC721 via ERC165
_registerInterface(_INTERFACE_ID_ERC721);
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
// Royalties interface
_registerInterface(_INTERFACE_ID_ERC721ROYALTIES);
}
```

**Checking if the NFT being purchased/sold on your marketplace implemented royalties (note using address.call() is completely **OPTIONAL** and is just one method)**

```
function checkRoyalties(address _token) internal returns(bool){
(bool success,) = address(_token).call(abi.encodeWithSignature("royaltyInfo()"));
return success;
}
```

**Transferring funds and calling the event to be emitted**

```
_recipient.transfer(amount);
IERC1776(_tokenAddress).royaltiesReceived(_recipient, _buyer, amount);
VexyCats marked this conversation as resolved.
Show resolved Hide resolved
```


## Rationale

There are many marketplaces for NFTs and the ones that support royalties are using their own standard version of royalties that are not compatible with other marketplaces. Just as in the early days of Ethereum, smart contracts are varied by ecosystem and not compatible. This would solve that issue such that an NFT created, purchased, or sold on one marketplace, provides the royalties that the creator is entitled too, regardless of the next marketplace/ecosystem it is sold at.

Without a set standard of way to do royalties, we won't have any effective means to provide royalties across the board, this obviously hampers the growth and adoption of NFTs, as the flexibility of the token and the UX is poor.

*"Yes we have royalties, but if your NFT is sold on another marketplace, we cannot provide royalties" .... "But can't I sell my NFT anywhere with a click of my wallet?" .... "Yes... but we don't have a standard for royalties so you'll lose out"*

This is poor UX and easily fixed with an accepted standard.
VexyCats marked this conversation as resolved.
Show resolved Hide resolved


## Backwards Compatibility

Completely compatible with current ERC-721 standards - in fact it requires it.

## Security Considerations

There are no security considerations related directly to the implementation of this standard.

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).