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

Discussion for EIP-2981: NFT Royalty Standard #2907

Closed
VexyCats opened this issue Aug 26, 2020 · 670 comments
Closed

Discussion for EIP-2981: NFT Royalty Standard #2907

VexyCats opened this issue Aug 26, 2020 · 670 comments

Comments

@VexyCats
Copy link
Contributor

VexyCats commented Aug 26, 2020

TL;DR - ERC721s have a ton of creators and a few marketplaces, but no accepted means for transferring royalties from items being sold multiple times on secondary sales. This EIP is proposing a standard method that can be implemented by all marketplaces easily.

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC721Royalties {
    
    
    /**
    @notice This event is emitted when royalties are received.

    @dev The marketplace would call royaltiesRecieved() function so that the NFT contracts emits this event.

    @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 RecievedRoyalties (address indexed creator, address indexed buyer, uint256 indexed amount);
    
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
    
     /**
     * @dev Returns true if implemented
     * 
     * @dev this is how the marketplace can see if the contract has royalties, other than using the supportsInterface() call.
     */
    function hasRoyalties() external view returns (bool);

     /**
     * @dev Returns uint256 of the amount of percentage the royalty is set to. For example, if 1%, would return "1", if 50%, would return "50"
     * 
     * @dev Marketplaces would need to call this during the purchase function of their marketplace - and then implement the transfer of that amount on their end
     */
    function royaltyAmount() external view returns (uint256);
    
      /**
     * @dev Returns royalty amount as uint256 and address where royalties should go. 
     * 
     * @dev Marketplaces would need to call this during the purchase function of their marketplace - and then implement the transfer of that amount on their end
     */
    function royaltyInfo() external view returns (uint256, address);
    
      /**
     * @dev Called by the marketplace after the transfer of royalties has happened, so that the contract has a record 
     * @dev emits RecievedRoyalties event;
     * 
     * @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
     */
    function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external view;
}

Flow: (just suggestions, can be implemented however you like)

Constructor/deployment

Creator - the person who gets the royalties for secondary sales is set.
Royalty Amount - the percentage amount that the creator gets on each sale, is set.

NFT sold on marketplace

Marketplace checks if the NFT being sold has royalties implemented - if so, call royaltyInfo() to get the amount and the creator's address.

Calculates the amount needed to be transferred and then executes that transfer.

Calls royaltiesRecieved() so that the NFT contract has a record of receiving the funds during a sale.


Thoughts? Anything that should be added or removed to make it easier to be implemented?

The logical code looks something like this:

abstract contract Royalties {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    uint256 private royalty_amount;
    address private creator;
 
    /**
    @notice This event is emitted when royalties are transfered.

    @dev The marketplace would emit this event from their contracts. Or they would call royaltiesRecieved() 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 RecievedRoyalties (address indexed creator, address indexed buyer, uint256 indexed amount);
    constructor (uint256 _amount, address _creator) internal {
        royalty_amount = _amount;
        creator = _creator;
    }

    
    function hasRoyalties() public pure returns (bool) {
        return true;
    }


    function royaltyAmount() public view returns (uint256) {
        return royalty_amount;
    }
    
    function royaltyInfo() external view returns (uint256, address){
        return (royalty_amount, creator);
    }
    
    function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external {
        emit RecievedRoyalties(_creator, _buyer, _amount);
    }
}
@MicahZoltu
Copy link
Contributor

What stops people from just transferring the asset via a wrapper to avoid paying on-transfer royalties? Perhaps time based royalties would be better, as those cannot be avoided (essentially, you are renting the asset rather than buying it).

@VexyCats
Copy link
Contributor Author

VexyCats commented Aug 26, 2020

@MicahZoltu there is no implementation for on-transfer royalties. The NFT marketplace that facilities the trade would be the one who is doing the transfer of funds.

So the smart contracts for something like Opensea, or Mintable, would facilitate the sell, and during the function handling the sell and transfer from seller to buyer, it would check if there are royalties and if so - send the % of the sale to the creator.

This means that using a wrapper doesn't really work, as wrapping a NFT would change the information on most NFT marketplaces - such that your game item from say Gods Unchained, would not be under the same contract as all Gods Unchained items. (although this is mainly for artwork and other content not gods unchained cards, but the example still applies - wrapping an NFT would change how its displayed on marketplaces and most likely deter buyers, and/or be flagged by the marketplace as a scam/fake ).

@MicahZoltu
Copy link
Contributor

Would participation be voluntary by OpenSea or Mintable then? If so, why is a standard necessary? Is it just to provide a common mechanism by which individual NFTs can advertise their requested royalty rate?

@VexyCats
Copy link
Contributor Author

VexyCats commented Aug 26, 2020

Yes, participation is voluntary - just as following the ERC165 standard is voluntary.

Currently there are 3 NFT marketplaces that all use different formats of royalties and one NFT on one marketplace with royalties, does not get seen/abided by on others.

This proposal is simply a standard way to implement royalties that each marketplace can use, so that NFTs with royalties can be accepted across all marketplaces.

Of course if the marketplace chooses not to accept royalties that have been implemented then there is nothing that creator can do, other than not use that marketplace (but doesn't stop others from selling the NFT on there after purchase).

Hence the need for a standard way to implement royalties that can be accepted across the board.

@VexyCats
Copy link
Contributor Author

If anyone has a suggestion on an implementation for

function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external view;

Please let me know - currently, it emits the event RecievedRoyalties but on testing, I realized its public and anyone can spam that event with fake data.

Since most marketplaces do not send ETH via calling the method and sending ETH in the same call - making it payable and checking the msg.value also isn't a good way.

Open to suggestions.... it doesn't NEED to be included actually, its just a nice way for the creator/others to see that royalties have been received based off secondary sales, which otherwise would be hard to identify

@martinkrung
Copy link

martinkrung commented Sep 16, 2020

I have background in art, and a standard for royalties would be critical for market adoption. This would be a big shift for artist. Because even successful artist do not get any share if their work is resold in the secondary market.

They sell cheap when they are unknown and if they have success all the others are making a fortune out of it. I even think this would lead to better quality. Because many artists tend to get in a loop and produce more of the same: the market wants recognizable art, and they need to make a living out of it, so they end up delivering the same shit over and over again.

I did some research like 20 years back and this is only partly true. For most small-town artist there is no secondary market anyway. But crypto may change this.

And about wrapping: If NFT are really used in the high roll art market not respect the royalties will not happen, because nobody will buy something with shady legal background.

@jamesmorgan
Copy link

Hi everyone, a few thoughts on the standard and some use cases which may help:

  • It would be good to open the query methods up to be on a per token basis, some tokens may have different royalties than others and it may not be a platform wide fix %. Also if its per token the recipient address can be defined which may be a multi-sig or another construct which would allow joint access to it if the token is a collaborator.

  • It would be good to allow a list of recipients - often tokens are part of a collaboration and may nee the funds splitting up between a list of people.

Standards are great, thanks for driving this initiative.

@MicahZoltu
Copy link
Contributor

Open to suggestions.... it doesn't NEED to be included actually, its just a nice way for the creator/others to see that royalties have been received based off secondary sales, which otherwise would be hard to identify

Given that it can be spoofed maliciously, I would just remove it. Apps will have to do other things to monitor royalty payments.

@MicahZoltu
Copy link
Contributor

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.

@dievardump
Copy link

dievardump commented Sep 16, 2020

What about a

payRoyalties(address _royaltyRecipient, address _buyer) payable

that will actually perform a check that there is a value sent and redirect to _creator.
It would also fire the event RecievedRoyalties, to which I would personaly also add the caller, helping to identify what contract actually did the royalties payment.

@VexyCats
Copy link
Contributor Author

Will rename creator to `royaltyRecipient.

@dievardump - there is no way to check for a value other than if that value is over the lowest amount possible. As the contract doesn't know what the NFT sold for, and therefore doesn't know how much royalties should be transferred. Unless you have an alternative suggestion?

Also since the marketplace contract is already transferring funds from the buyer to the seller - I believe its best to leave that responsibility on them and not the standard `to transfer the actual royalty payment.

@MicahZoltu For the event - what about a requirement that the marketplace must emit that event if they follow the standard? That would be the only requirement that they would need to follow in using this standard?

@VexyCats
Copy link
Contributor Author

I've updated the EIP to reflect the comments, and suggestions from everyone.

Including adding independent token royalties, such that one NFT can have a royalty amount different from another.

I've changed the percentage to a fixed point scaled to 10,000 as well to offer lower percentages possible.

Please review and let me know if anyone has any feedback

@dievardump
Copy link

dievardump commented Sep 17, 2020

@VexyCats - The problem is, as long as royaltiesRecieved doesn't perform a check if the current call has any value in it, anybody can call it with a fake amount, which will result in a fake event. Or a contract could say "I paid the fee" but didn't.
I didn't mean for payRoyalties to check that the value is the right one, I mean for payRoyalties to check that there is any value with the current call (if this NFT has a royalty fee), and that this value is used as the event royalties amount (and also transferred to royaltyRecipient/store for royaltyRecipient to claim later). This way, there is no spoof possible, as the event will correspond to a real value transferred.

We are currently developing an app that will onboard artists and the royalties question is very present. There are several ways to try to put it in place, but a standard that could be followed by the community and especially the marketplaces would be much welcome.

To be honest, a standard where the Marketplace would call a function on the ERC721 contract any time something is bought, even if there is no royalties, would also be very welcome, as this would facilitate for example to activate a lot more different system than just royalties on buy, but also for example royalties in the form of the Hardberger tax (or any other you can name).

So a more generic function like the one following (also with a token equivalent as not everything is sold using ETH):

 /**
     * @dev Called by the marketplace when a sell happened
     * @dev Expects msg.value to be of the amount of the royalties if there are 
     * @dev can emit RecievedRoyalties event;
     * 
     * @param _index Index of the NFT
     * @param _buyer The person buying the NFT
     * @param _soldAmount The amount this NFT has been bought
     */
function marketSell(uint _index, address _buyer, uint256 _soldAmount) external payable;

then if there are royalties on the specified nft, the contract could verify that the call value correspond to the percentage, send the value to the royaltiesRecipient, and emit the event.

But if for example there is a Harbergertax in place (which is also Royalties), it could easily store the sell price, and start the process of getting taxation.
This would allow a brand new horizon for royalties systems, as long as Marketplaces follow the standard.

About the current implementation :

  • I would also do everything per NFT id
  • I would remove hasRoyalties(), and just use royaltyAmount(uint index), which would return 0 if there is none. That saves one external call.

@VexyCats
Copy link
Contributor Author

@dievardump Thanks for the feedback, here are my thoughts.

Accepting payment on the function to emit the event, therefore marking the function as payable as your code does - would not work. As the creator should be getting the royalties and not the contract. Implementing a payment to the contract would require all royalty enabled contracts to handle the withdrawal of royalties on their NFT contract.

Also - checking for a msg.value doesn't really solve anything in terms of spoofing - I can simply send 0.0000000000001 eth as the value (pretty much worthless amount) and it would fire the event. I'd pay more in a gas fee than the amount sent to spoof.

Finally, all of this comes down to implementation details and not standard specific details. So it really doesn't matter/fit into the scope of the standard itself but rather how someone chooses to implement it.

I do understand an event spoofing might be an issue - although.... its not really an issue though?

Hear me out: If an event if fired - it doesn't affect anything, any person.... it would only affect a dapp using the events to log royalties. But even then - the dapp can simply check if there was actual ETH sent to the address specified when that event was fired and if so, log it as a true/successful payment. If it was spoofed and there was no ETH value then it doesn't get logged.

So I don't think the royalties event being spoofed is an issue at all - although if it doesn't fit the criteria for a standard then we should discuss a way to rework it.

Again - its not technically needed, but its one way to provide a means of notification that says - you received a royalty payment. Otherwise - how would a person be notified?

@MicahZoltu you've been quite helpful here - any thoughts?

@dievardump
Copy link

dievardump commented Sep 18, 2020

So I don't think the royalties event being spoofed is an issue at all - although if it doesn't fit the criteria for a standard then we should discuss a way to rework it.

When you say this, you seems to have no idea of the legal implications of royalties. Including taxes. There are tons of things to take into account, and having a NFT contract saying "this person got paid royalties" when they didn't is really not something artists can afford. Especially when not all artists are living good from their art.

Also, how can the dApp check that royalties were paid? What transaction to look for? When? What Block?

Implementing a payment to the contract would require all royalty enabled contracts to handle the withdrawal of royalties on their NFT contract.

Which could be added in this standard, withdrawRoyalties and make it better.

I do understand an event spoofing might be an issue - although.... its not really an issue though?

Ethereum is still not mainstream, and the cool and funny kids are still not on it. Wait for PoS, normal fees, and the democratization of having wallets and sending transactions, and you will always have people playing with what they can. The fact that it is not, but might be is enough to not allow this. We're talking about a standard here.

Finally, all of this comes down to implementation details and not standard specific details. So it really doesn't matter/fit into the scope of the standard itself but rather how someone chooses to implement it.

Which is why I think a royaltyAmount / marketSell / RecievedRoyalties event fired on actual royalties payment flow would be more fitting for a broader standard that allows all kind of rayalties and not only royalties %age on sell

As I said, we are really looking forward a royalties standard, because artists really need it.
But there are a lot of royalties systems and the royalties payments have to be identified for artists to be able to declare it.
A standard firing an event RecievedRoyalties without actually making sure that any amount was sent can't really be trusted

Also it's not only about art, but about the whole NFT ecosystem. Games, Arts, Deeds, everything can then have royalties (games could get a part of any resale, in order for example to create more items etc..), companies need a way to be able to know why a transaction was made to their contract (at least for legal purpose), and having those events being the most trustful possible is of higher importance in a standard

@VexyCats
Copy link
Contributor Author

VexyCats commented Sep 20, 2020

@dievardump The event being called with zero value - has no legal implications - as there was no royalties paid. Tax organizations don't charge tax from an event being called but a transfer of value.

If someone calls the event without sending a value - as a joke or way to troll the person - the person would only be affected by looking at their contract, seeing the event called, and then seeing there was no money transferred. Which is a pretty bad UX for them - but not anything that impacts the artist - as you said - 'something they cannot afford'. Also when you say there are legal implications of royalties - I'm 100% positive there is zero legal implication of an event being fired from a smart contract. Feel free to share the legal standing/law that discusses royalties and smart contract events, but I don't think any law covers events from smart contracts - although royalty payments are covered, an event being fired would not fall into that unless a payment was sent with it.

Withdraw system for 1% royalties on a 50 dollar NFT would be more in gas fees to withdrawal than the royalties earned.

There is no way to have the event be fired only with a valid value without having the smart contract hold the funds for each contract using royalties. Even if you checked for a value, as mentioned above, you could still send 0.0000000001 eth as a value and it would pass the checks.

The event doesn't need to be included in the standard - it could simply be removed as Micah suggested as well - it only serves the purpose of helping dapps and being a slightly better UX for creators being able to see an event instead of having to look for an internal transaction on their account.

@wighawag
Copy link
Contributor

Hi @VexyCats great initiative!
I like the voluntary aspect of this proposal that embrace the impossibility to enforce royalty on-chain

I got few suggestion

Current the contract expect one royaltyReceiver for the whole contract. This is not always how it work. sometime a NFT contract can have multiple "creators".

As such I would add a tokenID parameter to the functions

This is how I would see each functions:

function royaltyAmount(uint256 tokenID) external view returns (uint256);
This would allow different rate for different NFT.
Another thing I would change is the percent. it should be more granular. I would at least go for 10,000th
this function returns zero when the token has no royalty

function royaltyInfo(uint256 tokenID) external view returns (uint256, address);
This would allow to have different royalty receiver per token

More comments :

I would remove both royaltiesRecieved function and RecievedRoyalties as there is no guaranteed they will be called and as mentioned could be spam.

I would also remove hasRoyalties as the same can be achieved with royaltyAmount and ERC165 supportsInterface

@VexyCats
Copy link
Contributor Author

Thanks for the feedback @wighawag.

As for the percent on royalty - it has been changed to 10,000th in the EIP (atleast I think I updated that), pretty sure it is though. I agree, having ability to do 0.5% fee or something similar is best.

I think after thinking about it for awhile, removing the event all together is probably best as well.

And agreed that ERC165 can remove the hasRoyalties function.

Will make these updates and try to get it done shortly.

@dievardump
Copy link

dievardump commented Oct 21, 2020

Would you be open to a system that would make this "direct sales" royalties work, but also open the door to other types of royalties?

Marketplaces which will put in place this standard are places that do understand that royalties are important. Why come with a half solution?

What if there are more than one person declared as recipient of the royalties (for artists teaming to do a piece for example)?
-> the ERC721 contract implementing the royalties should handle the dispatch of the fee

What if the royalties amount varies depending on the net sale?
-> the ERC721 contract should be communicated the current amount the token is sold for

this could be handled by two functions :

/**
     * @dev Returns the amount of fees that should be paid. (not the pourcentage, the amount)
     *
     * @param _tokenId Index of the NFT
     * @param _soldAmount The amount this NFT would be bought for
     */
function royaltyAmount(uint256 _tokenId, uint256 _soldAmount) external view returns (uint256);

 /**
     * @dev Called by the marketplace when a sell happened
     * @dev Expects msg.value to be of the amount of the royalties if there are 
     * 
     * @param _tokenId Index of the NFT
     * @param _buyer The person buying the NFT
     * @param _soldAmount The amount this NFT has been bought for
     */
function marketSell(uint _tokenId, address _buyer, uint256 _soldAmount) external payable;

This allows to Track for secondary market sell.
This allows to put in place all sort of Royalties (Direct sale, Harberger Tax, ....)
This allows a much wieder range of handling of royalties than just one NFT id => one recipient
This allows varying royalties depending on the amount of the sale. (Royalties are sometimes capped to a maximum)

This by considering that the contract implementing the royalties actually knows better how to dispatch them because the Creator actually has contact with it. And so by letting it do the transfer of the royalties paid.

@jamesmorgan
Copy link

jamesmorgan commented Nov 4, 2020

I guess to @dievardump point I also agree that implementers of this will know and understand the concept well. One idea I have been playing with a simple query method allowing for any marketplace to handle royalties correctly based on multiple parties. Providing them an interface to get a list of creators of a token and all fees associated. Also leaving the funds payment part down to the caller.

function getRoyalties(uint256 _tokenId) external view returns (uint256[] _percentages, address[] _recipients);

This sort of things does obviously have problem in terms of GAS, a general smell of using arrays and a reliance on the caller to properly adhere to the rules. I know that for example KnownOrigin, which supports multiple collaborators, would not be able to use the single token royalty address/amount concept for many of its tokens due to them having multiple collaborators.

@wighawag
Copy link
Contributor

wighawag commented Nov 4, 2020

@jamesmorgan this could be handled by the recipient being a contract that then have the rules about percentage and recipient addresses.
This way marketplace do not need to deal with it. they simply send the fund to that contract.

@dievardump
Copy link

dievardump commented Nov 4, 2020

@jamesmorgan this could be handled by the recipient being a contract that then have the rules about percentage and recipient addresses.
This way marketplace do not need to deal with it. they simply send the fund to that contract.

Which would exactly be like sending to the NFT contract which knows percentages and recipients, except that with your way people would have to deploy a contract every time they make a commune piece.

I agree with you, marketplace shouldn't have to manage this. they should ask "how much do you want as royalties if I sell this NFT for this price", get a value, and send the value to the contract, which decides what it does with it.

@wighawag
Copy link
Contributor

wighawag commented Nov 4, 2020

@dievardump if you want to have the logic inside the NFT contract itself, it is fine too. I am just saying that it does not need to be part of the getRoyalties function signature

@jamesmorgan
Copy link

Hi all, thought I would pick this back up.

I have been thinking mainly about a few things in relation to the EIP, those being:

  1. How to handle multiple recipients of royalties - a common use case in art where the the NFT is a collaboration and royalties can be split between them. These collabs are not always 50/50 as well.

  2. How to handle 721 contracts where multiple user mint from the same contract - think of KnownOrigin, SuperRare, AsynArt etc - money cannot simply be sent to the main NFT contract without specifying its purpose i.e. the token ID it relates to, otherwise there is no way of know what the money if for and how to split it.

  3. GAS in general needs to be kept to a minimum in order for marketplaces to be able to adopt the standards.

  4. How to deal with bundles - for example OpenSea allows the sell of bundles of NFTs - these tokens may all have different creators and royalties amounts.

  5. Not all tokens have royalties

  6. Events - is this a necessary requirement?


Some ideas on solving this are:

  1. Deploying micro receiver contract/wallet for collaborators of NFTs where the funds can be sent and split accordingly - This will need to be a pull pattern to avoid GAS stipend problems unless all GAS is forwarded to the receiver. Also adds complexity and costs in relation to the token itself but these can be mitigated somewhat via various implementation types.

  2. This can be mitigated via receiving contracts in point 1 but assumes the sender will adhere to the returned value of the getRoyalty or similar.

  3. Keeping GAS low in my mind means keeping this as simple as possible, exposing a single method getRoyalty(tokenId) returns (address, unit256) - this way the marketplace can simply determine the amount and then transfer x funds to the returned sender.

  4. Transfering funds for a large bundle will get pricey - unsure of the best approach for this but I fear that it will introduce some batched/reconciliation process/requirements which mean that its less likely to be adopted, but I could be wrong here, its just a guess...

  5. Could use a try/catch or check 0x0000 (zero address) before sending

  6. Eventing is an interesting wrong - I believe this event should come from the receiver of the funds and not the marketplace sending it, this will remove the possibility of emitting an event different than msg.value.

Anyway, let me know your thoughts. These are the main points I have been thinking about in relation to this EIP.

@bardionson
Copy link

Is there any more progress on this draft? How can I help?

@VexyCats
Copy link
Contributor Author

Yeah! All help is welcomed.

Any suggestions or code changes you see or would like to add - feel free to do so/discuss here

@wildlifechorus
Copy link

I just started learning Solidity and blockchain development and I was looking for a proposal for royalties or secondary and tertiary sales and came across this proposal which is an excellent step in the right direction but I would like to ask a few questions, maybe there are some solutions to this.

  1. How can we solve the problem of NFTs that have several royalty recipients with different royalty percentages?
  2. How can we solve OTC (over-the-counter) deals or markets that don't want to implement royalties?
  3. Why rely on markets for royalty distribution? Can't we change transferFrom function that would receive address payable and handle all distribution of royalties?
  4. Assuming that we could add an address payable to the transferFrom that would handle all royalty payments could we set a minimum value on secondary or tertiary sales so we avoid OTC deals where the royalty recipients get nothing or almost nothing?

Maybe this is not the best place to ask these questions but these are the biggest issues I see right now with NFTs. Is that any other proposal than this one that addresses these issues?

function transferFrom(address payable from, address to, uint256 tokenId) public payable {
      super.transferFrom(from, to, tokenId);

      if (msg.value > 0) {
        // 10% to creator
        uint256 secondaryShare = msg.value.div(100).mul(10);
        _creatorOf(tokenId).transfer(valueToSend)

        // 90% seller
        from.transfer(msg.value - secondaryShare);
      }
    }

@jamesmorgan
Copy link

Welcome to the discussion @wildlifechorus and good questions.

Some thoughts on them from me:

  1. This is solvable if the royalties recipient address is a contract in its own right - meaning the contract that receives the funds can handle splitting between X number of participants. It would need to be a pull pattern as to split automatically would be a GAS heavy operation.

  2. This is harder to solve - you could put restrictions on the msg.value being present in the transfer but this may impede how freely a token can move about.

  3. Changing the transfer function could work but would slightly break the existing standards, also this would rely on marketplace passing the "correct" value done to the transfer hook.

Just some of my initial thoughts from your questions.

For me any standard like this need to try and not break existing token standards and be a GAS efficient at possible. If not efficient then marketplace are less likely to adopt as it will mean putting the GAS costs/onus back on the seller and may not be welcome.

@wildlifechorus
Copy link

@jamesmorgan Thanks for all your insights :)

Could you point me out to some resources on how I can understand better what you mean by this?:

  1. This is solvable if the royalties recipient address is a contract in its own right - meaning the contract that receives the funds can handle splitting between X number of participants. It would need to be a pull pattern as to split automatically would be a GAS heavy operation.

From what I understand what you mean is that a separate contract will:

  1. Receive the buy funds
  2. Pull the royalty addresses and percentages from a mapping in the NFT
  3. Split and transfer the money

PS: Again I'm sorry if I'm saying something silly, I'm very new to this.

@cliffhall
Copy link
Contributor

@asaadxheikh you should search back through this year+ long thread for discussion of how to set up a funds splitter contract. Also, what @frangio said.

@cliffhall
Copy link
Contributor

📣 New ERC-2981 Marketplace Open-Sourced

The Seen.Haus V3 Marketplace contracts were just open sourced after an extensive audit, and they implement the ERC-2981 standard for both primary and secondary markets. New NFTs have it built in, and foreign ones that are resold on secondary are also respected if they support the standard.

I was building the suite back when this EIP was still in heated discussion but fortunately it had reached a last-call state by the time they were complete. It follows the final standard. This exact implementation may not be useful as a direct model for anyone, as it is only one feature within the broader codebase, but it might be worth having a look for anyone who is interested in seeing an implementation in the wild.

The royaltyInfo method implementation is straightforward. Every NFT has an associated creator and a royalty percentage, so the calculation here is simple.

It is the final disposition of funds that has been most discussed on this thread. How are they calculated? The spec doesn't dictate this, but If the marketplace takes their fee first, then the royalty would be calculated based on a smaller amount, which many including myself consider unfair.

With the Seen.Haus implementation, when a sale or auction has completed and the funds are being disbursed, the royalties are calculated and paid first, then marketplace fees (which are further split between treasury and staking) are paid, and the remaining balance goes to the seller.

@nachomazzara
Copy link
Contributor

nachomazzara commented Mar 9, 2022

Have you evaluated

Hi @VexyCats great initiative! I like the voluntary aspect of this proposal that embrace the impossibility to enforce royalty on-chain

I got few suggestion

Current the contract expect one royaltyReceiver for the whole contract. This is not always how it work. sometime a NFT contract can have multiple "creators".

As such I would add a tokenID parameter to the functions

This is how I would see each functions:

function royaltyAmount(uint256 tokenID) external view returns (uint256); This would allow different rate for different NFT. Another thing I would change is the percent. it should be more granular. I would at least go for 10,000th this function returns zero when the token has no royalty

function royaltyInfo(uint256 tokenID) external view returns (uint256, address); This would allow to have different royalty receiver per token

More comments :

I would remove both royaltiesRecieved function and RecievedRoyalties as there is no guaranteed they will be called and as mentioned could be spam.

I would also remove hasRoyalties as the same can be achieved with royaltyAmount and ERC165 supportsInterface

What if you store the last computed balance by erc20 on each onRoyaltiesRecieved and therefore you have something like

    // Can be optimized but it is just a legible example
    function onRoyaltiesRecieved(address _beneficiary, address _buyer, address _erc20, uint256 _amount) 
       external returns (bytes4) 
    {
      ...
      require(_amount > 0, "INVALID_AMOUNT");
      uint256 currentBalance = IERC20(_erc20).balanceOf(address(this));
      require(lastComputedAmount[_erc20] + _amount <= currentBalance, "NOT_RECIEVED_AMOUNT");
      lastComputedAmount[_erc20] = currentBalance;
      ...
      return ON_ROYALTIES_RECIEVED;
    };

If someone calls it publicly, at least they had transferred the token and it can be seen as a donation/contribution.

Am I not seeing something? sorry, I'm too late here :(

@StEvUgnIn
Copy link

Hello, I have been toying around ERC2981 to manage royalties and the standard is really clear regarding royalty currency and recipient address.

I guess to @dievardump point I also agree that implementers of this will know and understand the concept well. One idea I have been playing with a simple query method allowing for any marketplace to handle royalties correctly based on multiple parties. Providing them an interface to get a list of creators of a token and all fees associated. Also leaving the funds payment part down to the caller.

Thinking in terms of theory and prospect, this standard offers an excellent solution for managing royalties for one or multiple parties.

You can define a smart contract wallet or create a multisig wallet (e.g., Gnosis Safe) for managing funds with multiple parties. Once, your contract is ready to use. You can assign the deployment address a recipient address so that royalties will be split/shared after each sale (depending on how you implement invoice for specific tokens and ether).

The returned address is not described to be specific (could be a contract or a user wallet). EIP-2981 does not either specify a currency or token used for the sale / auction. 🙈

@dngreen04
Copy link

Has this standard been adopted? If not is there a method to ensure that a creator receives royalties on different marketplaces? e.g. mint on Opensea and then an owner sells on Rarible

@cliffhall
Copy link
Contributor

Has this standard been adopted? If not is there a method to ensure that a creator receives royalties on different marketplaces? e.g. mint on Opensea and then an owner sells on Rarible

I know the standard has been implemented at KnownOrigin and Seen.Haus (I worked on those).

It is up to individual marketplaces like OpenSea and Rarible to adopt the standard, which is basically just a query: for given price x, what amount y should be paid as a royalty and to whom.

Support of the standard is two-sided, and marketplaces should support both, but there's no way to make them.

If OpenSea supports the standard by exposing the royaltyInfo method on their NFT contract, then Rarible could call it and learn what royalty is owed. However , if OpenSea doesn't check to see if Rarible supports the standard and pay royalties on NFTs minted there, then they're only supporting the standard on one side. We hope marketplaces will support it fully.

@MaxflowO2
Copy link

In this case: Use a funds splitter contract as the royalty recipient.
It receives ETH or tokens and tracks the collaborators and the percentages for the funds split. Any collaborator can call a function to drain the funds in the contract, which then disburses the funds according to the percent for each collaborator.
https://github.com/knownorigin/known-origin-contracts-v3/blob/master/contracts/spikes/collaborators/handlers/FundsReceiver.sol

This right here is the best way to implement that. Just invoke a new PaymentSplitter for royalties, personally a fan of that and then boom done.

@MaxflowO2 Thanks. The feedback at mintpress was that using a funds splitter contract was prohibitively expensive, since it could cost as much as 500 USD to deploy. I neglected to mention that the architecture of a system using funds splitter contracts might want to employ the minimal clone strategy rather than deploy a new instance of the funds splitter contract every time.

So, along with the above link to a funds splitter contract implementation, anyone considering this approach may also wish to look at this implementation of a royalty setup that clones the funds handler as a minimal proxy (at a much reduced cost):

https://github.com/knownorigin/known-origin-contracts-v3/blob/master/contracts/spikes/collaborators/clone/CloneBasedRegistry.sol#L30

Sorry @cliffhall, just went back over the discussion on the payment splitters... I might have something similar to what you got in those links. Been upgrading all my base sets recently again... (got to love the bear market).

@JayWelsh
Copy link

JayWelsh commented Apr 9, 2022

Here is another example implementation in case it helps anyone: https://github.com/JayWelsh/eip-2981-example

@vandeven22
Copy link

Working as an artist I wonder if it would be possible for creator to have the work deleted if It is sold without the agreed royalty, and the creator be notified when this happens? Creator can then re-mint the artwork. My 2nd Q is: when will ERC 2981 for universal royalty be implemented at major art platforms?

@freeslugs
Copy link

@vandeven22 sounds like a cool idea -- to my knowledge, eip2981 is opt-in, marketplaces e.g. looksrare need to opt in to honoring the royalty, and there's no way to enforce it. you could technically override the transfer / safe transfer methods, but it would likely cause a jarring UX, since i imagine most contracts call those methods externally. Remember OTC deals + gifting NFTs, those would be hard to avoid.

@vandeven22
Copy link

Thanks.
To my regret, I don't know anything about technical aspects. Please excuse a silly question: why should gifting NFT's or OTC transactions be royalty-free? Wouldn't it just be possible to graft the artist's choice to have the nft deleted onto the nft when it is sold without copyright?

@cliffhall
Copy link
Contributor

cliffhall commented Apr 12, 2022

why should gifting NFT's or OTC transactions be royalty-free?

Simple transfers cannot be distinguished from an NFT's owner simply moving it to a cold wallet for safekeeping.

Or imagine a hacker has gained your seed phrase. You're now in a race against the clock. You both control the wallet. You must now triage and move as many of your beloved assets over to a new wallet as quickly as possible while the hacker does the same.

Surely you would not expect the owner to pay an extra royalty for the convenience of moving between wallets they control.

In the case of gifting, there is no price to calculate the royalty from. 10% of nothing is nothing.

If payment happens out of band in an OTC transaction, it would be proper for the parties to observe the royalty, but there is no way to enforce it. The NFT standard doesn't have any concept of commerce, only ownership and transferability.

@vandeven22
Copy link

vandeven22 commented Apr 15, 2022

Thank you. I have no education in programming (artist/economist) so please excuse a silly question:
What makes it necessary for these very different purposes, like making a gift or transfer to other wallet, to be a one size fits all transaction? Is it not possible to distinguish a gift or a transfer transaction (without royalty) from a sales transaction (with royalty)? So that, when the gift or the transferred NFT is subsequently sold, the royalty contract will again be executed?

@freeslugs
Copy link

@vandeven22 generally to my knowledge - the sales txs implemented by marketplaces would honor the 2981 fee and use the safeTransferFrom method under the hood, whereas the gifting would just hit the safeTransferFrom method directly.

https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#IERC721-safeTransferFrom-address-address-uint256-bytes-

@MicahZoltu
Copy link
Contributor

Closing this for housekeeping purposes since this EIP is final, but feel free to continue discussing here as needed.

@Ajikhan
Copy link

Ajikhan commented Aug 1, 2022

TL;DR - ERC721s have a ton of creators and a few marketplaces, but no accepted means for transferring royalties from items being sold multiple times on secondary sales. This EIP is proposing a standard method that can be implemented by all marketplaces easily.

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC721Royalties {
    
    
    /**
    @notice This event is emitted when royalties are received.

    @dev The marketplace would call royaltiesRecieved() function so that the NFT contracts emits this event.

    @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 RecievedRoyalties (address indexed creator, address indexed buyer, uint256 indexed amount);
    
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
    
     /**
     * @dev Returns true if implemented
     * 
     * @dev this is how the marketplace can see if the contract has royalties, other than using the supportsInterface() call.
     */
    function hasRoyalties() external view returns (bool);

     /**
     * @dev Returns uint256 of the amount of percentage the royalty is set to. For example, if 1%, would return "1", if 50%, would return "50"
     * 
     * @dev Marketplaces would need to call this during the purchase function of their marketplace - and then implement the transfer of that amount on their end
     */
    function royaltyAmount() external view returns (uint256);
    
      /**
     * @dev Returns royalty amount as uint256 and address where royalties should go. 
     * 
     * @dev Marketplaces would need to call this during the purchase function of their marketplace - and then implement the transfer of that amount on their end
     */
    function royaltyInfo() external view returns (uint256, address);
    
      /**
     * @dev Called by the marketplace after the transfer of royalties has happened, so that the contract has a record 
     * @dev emits RecievedRoyalties event;
     * 
     * @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
     */
    function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external view;
}

Flow: (just suggestions, can be implemented however you like)

Constructor/deployment

Creator - the person who gets the royalties for secondary sales is set. Royalty Amount - the percentage amount that the creator gets on each sale, is set.

NFT sold on marketplace

Marketplace checks if the NFT being sold has royalties implemented - if so, call royaltyInfo() to get the amount and the creator's address.

Calculates the amount needed to be transferred and then executes that transfer.

Calls royaltiesRecieved() so that the NFT contract has a record of receiving the funds during a sale.

Thoughts? Anything that should be added or removed to make it easier to be implemented?

The logical code looks something like this:

abstract contract Royalties {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    uint256 private royalty_amount;
    address private creator;
 
    /**
    @notice This event is emitted when royalties are transfered.

    @dev The marketplace would emit this event from their contracts. Or they would call royaltiesRecieved() 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 RecievedRoyalties (address indexed creator, address indexed buyer, uint256 indexed amount);
    constructor (uint256 _amount, address _creator) internal {
        royalty_amount = _amount;
        creator = _creator;
    }

    
    function hasRoyalties() public pure returns (bool) {
        return true;
    }


    function royaltyAmount() public view returns (uint256) {
        return royalty_amount;
    }
    
    function royaltyInfo() external view returns (uint256, address){
        return (royalty_amount, creator);
    }
    
    function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external {
        emit RecievedRoyalties(_creator, _buyer, _amount);
    }
}

@VexyCats i just want to ask you that is this possible that admin mint NFT and artist get their royalty?
both are diffrent persons.

@Ajikhan
Copy link

Ajikhan commented Aug 1, 2022

@arpu @nullren @jamesmorgan @NHQ @frangio @EvilJordan @frangio
i just want to ask you that is this possible that admin mint NFT and artist get their royalty?
both are diffrent persons.

@ppk2533
Copy link

ppk2533 commented Aug 14, 2022

@arpu @nullren @jamesmorgan @NHQ @frangio @EvilJordan @frangio ฉันแค่อยากจะถามคุณว่าเป็นไปได้ไหมที่ผู้ดูแลระบบ Mint NFT และศิลปินจะได้รับค่าลิขสิทธิ์ ทั้งสองเป็นบุคคลที่แตกต่างกัน

แน่นอนครับ

@ppk2533
Copy link

ppk2533 commented Aug 14, 2022

#5457

@EdoLom
Copy link

EdoLom commented Jan 12, 2023

After the whole debacle of marketplaces ignoring royalties one after the other, until the creation of a shared blacklist of rogue marketplaces tried to salvage the situation, I came to this page in search of answers, as I could not understand how the instructions of a smart contract could be avoided.

The answer I found here baffled me even more: the royalties payment system is an optional information system that can be skipped. Even though I can understand this was thought at an early stage as a way to provide a norm that could be shared by most marketplaces, and even though it is always easier to judge in retrospect, it seems obvious that an optional system would degenerate into a race to the bottom. Why would a marketplace not favor their own market share rather than the artist’s fee? Why should a commercial company be trusted with moral responsibilities?

As for why the smart contract could not include the enforced payment of the royalties, the logical and technical reason given baffles me even more: royalties payment could be enforced because one cannot recognize when a transfer is a non-commercial move to another wallet of the same owner rather than a sale.

Why would it not be possible to systematically apply the royalties percentage payment since any percentage of a non-commercial 0 ETH transfer would always be 0 ETH? What am I missing here?

@EvilJordan
Copy link

@EdoLom Because you can wrap a token in your own/other contract and transfer THAT instead of the underlying asset with its own, different, on-chain rules.

@Lenoftawa
Copy link

Lenoftawa commented Jan 12, 2023

@EdoLom
You also need to allow for transfer between a user's wallets without incurring royalties.
That becomes a bigger issue if there is an attempt to enforce.
This is simply designed as a standard way of signalling the royalty. Other eips can deal with these issues.
I actually don't really buy the wrapping argument. If you wrap an NFT you will need to pay the royalty twice so that the next purchaser doesn't have to. Where did you benefit? I can't see a wrapped NFT attracting a premium above the royalty that was spent wrapping it. In fact I would see it as permanently damaged goods and sell for less.
(Or maybe I don't understand how wrapping would work)

@richardrauser
Copy link

@EdoLom So then what's to stop a seller from sending the NFT with 0 payment, while the buyer sends ETH in a separate transaction? What's to stop me from publishing a new smart contract that interacts with the existing NFT contract to consolidate that process?

@Lenoftawa
Copy link

Lenoftawa commented Jan 12, 2023

@EdoLom So then what's to stop a seller from sending the NFT with 0 payment, while the buyer sends ETH in a separate transaction? What's to stop me from publishing a new smart contract that interacts with the existing NFT contract to consolidate that process?

I do think that there will be ways that the advantage of defeating the royalty can be minimised while allowing for transfers between personal wallets.

@dievardump
Copy link

dievardump commented Jan 13, 2023

Why would it not be possible to systematically apply the royalties percentage payment since any percentage of a non-commercial 0 ETH transfer would always be 0 ETH? What am I missing here?

Because:

  1. the blockchain does not allow you to know if there is any native value sent with the Transaction, only if there is any value sent with the call to the transfer function for this contract.
  2. Because the blockchain does not allow you to know if there is any token meant to be sent with the tx.
  3. Because the payment could be async in several tx: The item is escrowed. The buyer send ether to the escrow. The buyer claims the Token. No payment with the Tx that claims the token.
  4. ... so many more ways to go around

So you can not determine, at the contract level, if a tx is a payment or a personal transfer.

The reasons baffle you because you do not know the technicalities of how tokens transfers work.

@EdoLom
Copy link

EdoLom commented Feb 8, 2023

Thank you for all your answers. I never pretended I knew the technicalities of tokens transfers, which is exactly why I posted here asking what I was missing to understand the situation. Indeed, because the value can be transferred independently from the token, by asking the owner to transfer the token to a third-party escrow contract, there is no other way to absolutely enforce royalty payments than by blacklisting all non-conforming marketplaces. Besides, I feel that the current situation has stabilized and provides a satisfactory solution, especially as implemented by Manifold: use the CORI registry, or keep ownership of the blacklist.

@calebfergie
Copy link

Hey all, thank you for this information and @EdoLom thanks for asking the question in the first place. I am far from 'technical' myself and learned a lot by reading through this discussion.

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

No branches or pull requests