ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.95k stars 5.31k forks source link

Discussion for EIP-2981: NFT Royalty Standard #2907

Closed VexyCats closed 2 years ago

VexyCats commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

Standards are great, thanks for driving this initiative.

MicahZoltu commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 :

VexyCats commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

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 commented 3 years ago

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

VexyCats commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

jamesmorgan commented 3 years ago

Ye kind of like that @wildlifechorus

The contract address which receives the royalties funds just needs to know who to allocate them between and what percentages for each address. Each party could then pull out their accumulated royalties.

At a high level anyway.

bardionson commented 3 years ago

Yeah! All help is welcomed.

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

I am a programmer and artist but have zero experience with blockchain coding. It would take me a while to ramp up. Is there a mechanism for groups of people who would benefit from this feature sponsoring it somehow?

dievardump commented 3 years ago

@jamesmorgan

The contract address which receives the royalties funds just needs to know who to allocate them between and what percentages for each address. Each party could then pull out their accumulated royalties.

At a high level anyway.

What if the receiving contract was the NFT contract? This is the contract implementing Royalties, it should be the one to know how to handle it.

Process: Marketplace is selling an NFT Marketplace inquire on the NFT contract if this particular NFT has Royalties NFT contract returns an integer, between 0 and 10000 If integer between 0 and 10000, Marketplace calculates, and send the Royalties to the contract NFT contract handles royalties (most likely stores an integer and waits for recipients to claim).

Something like:

/**
 * This works with the idea that marketplaces shouldn't be the one managing how royalties are sent to recipients
 * If a contract declares supporting the royalties standard, then it should have a mechanism for distributing it
 *
 * Marketplaces should inquire if there are any royalties set for a token id
 * If yes, it should only send royalties to this contract (using onRoyaltiesReceived)
 * This contract is the one that knows how Royalties should be handled.
 *
 * -> Complexity of distributing royalties shouldn't be handled by the marketplaces
 */
interface INFTRoyalties is IERC165 {
    /**
    @notice This event is emitted when chain currency royalties are received.

    @param tokenId The token id for this NFT
    @param amount The amount received
  */
    event RoyaltiesReceived (uint256 indexed tokenId, uint256 amount);

    /**
     * @dev this is called by other contracts to enquire royalties for a given id
     * This returns the amount of royalties, in % (0 to 10000, 2 decimals) for an id
     * 
     * @param id token id
     */
    function getRoyalties(uint256 id) external view returns (uint256);

    /**
     * @dev this is called by other contracts to send royalties for a given id
     * This MUST emit RoyaltiesReceived()
     *
     * @param id token id
     * @return `bytes4(keccak256("onRoyaltiesReceived(uint256)"))`
     */
    function onRoyaltiesReceived(uint256 id) external payable returns (bytes4)
}

This could even be extended to ERC20 and erc1155 by simply adding address token and an uint256 id that are 0x0 when receiving ETH, but address set for erc20 and tokenId for erc1155, in both ̀onRoyaltiesReceived` and the event.

this adds 2 methods and one event to erc721 and erc1155, but then it's pretty straightforward and it ensures that:

this answers to several points (2, 3, 6, partially 1) of your post here: https://github.com/ethereum/EIPs/issues/2907#issuecomment-728015406

Bundles are a very complicated case. I'm not sure how this can be handled.

I'm starting to think that the best way would be to have one general contract that acts as RoyaltiesRegistry for all NFTs.

1/ NFT contracts would declared royalties on this registry 2/ Marketplace would all inquire on the registry if royalties are declared, if yes, do whatever the registry process would be.

this way we would have one "NFT Lego" put in place, that all NFT contracts can build on and all marketplaces can follow if they wish to.

jamesmorgan commented 3 years ago

Good points @dievardump, welcome to the conversation - this is actually along the same lines as the I am currently look to solve it as well.

I'm starting to think that the best way would be to have one general contract that acts as RoyaltiesRegistry for all NFTs. <- I think is also where my head is at, slightly differently.

Some example code I have looks a bit like this, idea being that when tokens have multiple collaborators you can define a small recipient for the funds. In theory these recipients could be a DAO, multi-sig or pretty much anything you want, as long as it can receive funds and then allow splitting them according to the config, using a pull pattern.

    function royaltyInfo(uint256 _tokenId)
    external
    override 
    returns (address receiver, uint256 amount) {
        if (royaltiesRegistryProxy != address(0x0)) {
            return royaltiesRegistryProxy.royaltyInfo(_tokenId);
        }
        return (originalCreator, roylatyAmount);
    }

This way it's simple for the integrator, but I admit it does introduce some UX complexities in setting these up from a creator perspective.

  1. Marketplace asks NFT contract where to send any royalties
  2. NFT check if it has a handler defined for that token
  3. If so sends new recipient address/amount back | if not defined just send the original single creator
  4. Marketplace sends the funds to the receiver
  5. Collaborators then can program how they handle the funds

Could even use some funky CREATE2 logic to predetermine the addresses so you don't need to deploy a handler until you have some funds in place to save the GAS.

I personally like separating the funds handling from the NFT but that's just stylistic and it would allow for more complex handling/rules over any royalties in the future, either way something along these lines would be great.

spelunkerofbasil commented 3 years ago

Hello everyone. I have been a professional artist for nearly 50 years. My forte is acrylic painting, but I started doing digital art in the mid-1980s (I wrote and illustrated the first children's book ever to be illustrated in color on a computer: 8-bit color on a Macintosh II. The tech was so new that no one knew how to get my illustrations out of the computer and into print! Finally got the files converted to 35mm color slides that the publisher used to create the 4-color separations.) Anyhow, I'm no superstar, but there IS a secondary market for my work. I made a comment on Twitter about this about a year ago--about how I am essentially competing with my younger self--and some forward-looking crypto artists got together on Discord to make a push for royalties. We "leaned on" a few of the platforms to implement a royalties system, and found them to be quite receptive. As a result, SuperRare, KnownOrigin and others came through with a 10% royalty. This has already been a godsend to some artists who report being able to live on royalty income during periods in which they are concentrating on creation rather than making sales.

So I just wanted to thank you all for working on this code which it seems to me would be appreciated by most of the cryptoart NFT platforms. I can do nothing to help you refine the code, but I CAN help create some buzz about it when the time is right. And I'm sure that the original group of crypto artists who banded together to take a stand for royalties would do so again to press for industry-wide adoption.

The issue of royalty payments to visual artists is hugely important. And the work you are doing could well help artists not yet born if it can become a defacto standard. I'm 73 years old and will likely never benefit much from NFT royalties. But my estate could. And so could visual artists of all ages all around the world.

I'm at LawrenceLeeArt.com.

DraFfe2 commented 3 years ago

Hey all, i have been working on an NFT project recently and found this EIP, i think it is just what we need! Having the ability to assign a different royalty amount for each token on the contract is some thing that i think is very important, allowing different creators or series of NFT’s to be minted from one ‘mother’ contract.

I was wondering how far away from an implementation this is? I am working on a project and started to build in my own Royalty system, then found this and obviously want to use a standardised interface. The way we are building our system would allow for the royalties to be paid back to a central wallet/contract then we would take care of any extra splitting of the royalties from event listeners.

Want to say great work on this, with the momentum NFTs are getting at the moment and the range of market platforms we really do need a standardised way of paying royalties to the creators!

Best Tuck

seibelj commented 3 years ago

I also agree this is important.

Let me flip the table and offer an alternate proposal.

We all agree that royalty payments are voluntary. The marketplace will have to agree to pay them, and it's on the artist / community to punish marketplace / people who don't pay them, through social pressure / shaming or potential legal remedies. I think this is fine, as marketplaces become larger they become easy to blacklist in the ERC721 smart contract, and if an NFT becomes highly valuable, its entire transfer history is public so the provenance can be questioned if needed and its value destroyed if the rules were violated. I anticipate all major NFT dealers and marketplaces will abide by royalty rules - everyone knows the rules, and the value of an NFT will be affected by the royalties set by the creator, so this isn't a surprise.

Since we all agree that royalty payments are voluntary, why don't we create a standard that is mostly IPFS (content-hash) based? A JSON format that contains all the edge cases, splits, tax issues, and so on. This format can be versioned to add features later.

When royalty is paid, the payer emits an event that references the CID of the JSON file containing the royalty rules in the same transaction in which they pay everyone. This allows third-parties to audit the royalty payment to ensure it matches the rules. Libraries can be written in any language that help construct the transaction properly based on the royalty JSON, so the marketplace doesn't have to implement any of the complex logic themselves.

The ways royalties are paid can be very complex. msg.value isn't enough because the fee might be paid in USDC or other ERC20 rather than ETH. We agree that gas fees will quickly get crazy and the marketplace won't want to implement this EIP if it increases costs, and given already absurd ETH gas costs, that seems likely. Furthermore, the crazy complexity of royalty logic means no solution we come up with here will be adequate, and the the smart contracts will get more edge cases, more risky and potential for hack / exploit / bug, and gas costs will keep rising.

Further benefits of the Royalty-spec-in-JSON plan:

One final note is that OpenSea already leverages IPFS for the metadata feature which is widely implemented, so using IPFS for further NFT features is not abnormal.

Thoughts?

spelunkerofbasil commented 3 years ago

Just to let know that I was part of the loose association of cryptoartists that moved royalty payments into the mainstream consciousness. I was quoted in an NBC news article on the issue. Many artists were planning to abandon platforms that did not implement decent royalties on secondary market sales. When/if the time comes to spread the word on new standards, we can again join the fray in support of the effort.

anthonygraignic commented 3 years ago

I also agree this is important.

Let me flip the table and offer an alternate proposal.

We all agree that royalty payments are voluntary. The marketplace will have to agree to pay them, and it's on the artist / community to punish marketplace / people who don't pay them, through social pressure / shaming or potential legal remedies. I think this is fine, as marketplaces become larger they become easy to blacklist in the ERC721 smart contract, and if an NFT becomes highly valuable, its entire transfer history is public so the provenance can be questioned if needed and its value destroyed if the rules were violated. I anticipate all major NFT dealers and marketplaces will abide by royalty rules - everyone knows the rules, and the value of an NFT will be affected by the royalties set by the creator, so this isn't a surprise.

Since we all agree that royalty payments are voluntary, why don't we create a standard that is mostly IPFS (content-hash) based? A JSON format that contains all the edge cases, splits, tax issues, and so on. This format can be versioned to add features later.

When royalty is paid, the payer emits an event that references the CID of the JSON file containing the royalty rules in the same transaction in which they pay everyone. This allows third-parties to audit the royalty payment to ensure it matches the rules. Libraries can be written in any language that help construct the transaction properly based on the royalty JSON, so the marketplace doesn't have to implement any of the complex logic themselves.

The ways royalties are paid can be very complex. msg.value isn't enough because the fee might be paid in USDC or other ERC20 rather than ETH. We agree that gas fees will quickly get crazy and the marketplace won't want to implement this EIP if it increases costs, and given already absurd ETH gas costs, that seems likely. Furthermore, the crazy complexity of royalty logic means no solution we come up with here will be adequate, and the the smart contracts will get more edge cases, more risky and potential for hack / exploit / bug, and gas costs will keep rising.

Further benefits of the Royalty-spec-in-JSON plan:

  • NFT token id is associated with royalty payments CID which can later be updated by NFT creator. The royalty info CID is tied to block number, so we always know which are valid for sales in a certain block.
  • Royalty payment could be batched - where a marketplace pays (say) once a month to a specific artist, and includes payments for many sales, all according to the rules in the royalty JSON blob.
  • Libraries written in any language that generate raw txs to pay creators. They fetch info from blockchain, from IPFS, generate payment TX. Takes all the headache off of marketplace / vendor
  • Rules are blockchain-agnostic, so while of course we are ETH-focused, the rules for payments of a given NFT don't have to be only payable on ETH chain, making the rules more legally-binding and allowable in L2 or other side chains.

One final note is that OpenSea already leverages IPFS for the metadata feature which is widely implemented, so using IPFS for further NFT features is not abnormal.

Thoughts?

I think that royalty payments shouldn't be voluntary and as soon as an artist publish an artwork on a royalty-compliant contract, this contract should ensure that the artist get its royalties by marketplaces. Also there is royalties legislation that covers the digital world, so an artist could at some point redeem them in front of a court (even if that would be complex but it could happens for big amount in a few years).

But I really like the idea of a Royalty spec in JSON to cover all the complex royalty systems all around the world and to guarantee to the artists that its author rights are declared with its NFT and should be respected. I think it should be a subpart of the NFT metadata file or a link to another JSON file respecting the same format with the version to tell what kind of royalty is implemented. It could be DID-backed with a Verifiable Claim so we can verify that it was the artist will. And as standard evolves, artists could updated their Royalty spec to get more royalties type and respond to specific-country uses cases for instance. This standard would allow blockchain-agnostic development and converts Authors' rights into computer-readable code. And with that and outside of this EIP, you could make royalty payments and trace them with some DID, but I think it out of this discussion.

So the question about this EIP for me is, should we keep something simple at the beginning with a standard royaltiesRegistryProxy that would allow marketplaces to easily know how much they need to pay and then the proxy contract shares it accordingly to the artist royalty spec. Or should we implement an 'NFT Lego' with different kind of interfaces to handle by each contract?

seibelj commented 3 years ago

I think that royalty payments shouldn't be voluntary and as soon as an artist publish an artwork on a royalty-compliant contract, this contract should ensure that the artist get its royalties by marketplaces.

I agree with you that in an ideal world, this would be the case. But with the size of code this could be, issues with gas costs, attack surface, changes to laws and regulations, maybe we should have 2 solutions. One with JSON and voluntary basis, the other "code is law". I think we can move faster with JSON and wait until ETH2 for "code is law" perfection, especially once the JSON version gets mature and changes less often. I think we all don't even know exactly what we need right now, and with NFT boom times, this issue is high priority for all of us in the ecosystem to get to some sort of agreement.

What's simple about my proposed solution is, the blockchain piece comes down to something like:

event RoyaltiesPaid (bytes32 indexed txsale, address indexed payer, address indexed token, uint256 indexed amount, bytes32 royaltyInfo)

txsale: The transaction hash referencing the sale of the NFT. This is important because the payment may happen later as part of a batch, and some transfers may not be sales at all (holder is moving token between their own addresses). By marking a specific transfer hash, this is notifying the blockchain that a sale occurred, and we are paying for it.

payer: Address of the payer, might be different from the sender of this transaction if they have the approval to spend on behalf of others.

token: 0x0 if native token (ETH), otherwise ERC20 address, from which the symbol and decimals can be retrieved

amount: total amount of token transferred.

RoyaltiesPaid would be emitted inside of the transaction in which the seller (or marketplace on behalf of seller) paid all royalty recipients in a single transaction.

Royalties can be rich metadata property inside of existing standard https://eips.ethereum.org/EIPS/eip-1155

Or we could add a map:

// Mapping from token ID to royalty CID
mapping(uint256 => bytes32) public royaltyInfo;

This maps each token ID to a specific CID containing JSON in the format we specify on how to pay. Updating logic can be added to control if and how this can be modified post-minting.

What does this all mean?

You would need a separate event for every payment, but we could create a batching function to call the event N times to make it easier.

dievardump commented 3 years ago

txsale: The transaction hash referencing the sale of the NFT. This is important because the payment may happen later as part of a batch, and some transfers may not be sales at all (holder is moving token between their own addresses). By marking a specific transfer hash, this is notifying the blockchain that a sale occurred, and we are paying for it.

control if and how this can be modified post-minting.

I strongly think that royalties shouldn't be modifiable after mint, or at least not after the first transfer

You need to remember that not all art is sold. People give it, exchange it, whatever. There is no reason to consider a transfer that didn't result into no royalties to be a "non compliant transfer".

For this reason, there is absolutely no way to force anyone to comply on a Royalty system. If you disallow a marketplace, they create a proxy contract for peanuts and can sell again.

JSON means off-chain, this is not possible. Not everything is centralized, DEX can't follow off-chain. And as seen with Uniswap, things tend to go Dex.

What we need are "NFT Legos" similar to "DeFi Legos" that contracts build on. But calculating and managing royalties off-chain is not a viable solution. The same way it's not viable to consider all transfer are sales.

Also, again, here people focus on "Direct sales royalties". There are a lot more type of royalties than this, we need to address a bigger problem. Added on top of that, rightly pointer by Anthony that there are laws (about paying those royalties but also the amount and value of it.)

seibelj commented 3 years ago

txsale: The transaction hash referencing the sale of the NFT. This is important because the payment may happen later as part of a batch, and some transfers may not be sales at all (holder is moving token between their own addresses). By marking a specific transfer hash, this is notifying the blockchain that a sale occurred, and we are paying for it.

  • you can't get a transaction hash from the transaction itself
  • Events are linked to transaction, no need to include it in the log

I think you misunderstand me - this event references a prior Transfer event that the royalty-payer is referencing when they pay the royalty. It could optionally be included inside the Transfer transaction itself, by setting txsale to empty.

This circles back to my original point - we cannot force users to pay a fee inside of transferFrom without killing usability. Not every transfer is a sale. Therefore, paying a royalty must be voluntary, as anyone trying to avoid royalties would just transfer and not pay. To force someone to pay, we would have to tie every transfer to an amount exchanged, introspect the value exchanged inside of the transaction, know all of the royalty details, and then split all the profits to the royalty receivers. And to get around this, all a seller would have to do is move it to an escrow contract and pay the minimum fee in and out.

Which gets back to the original point of this EIP - it is a proposal for a voluntary royalty payment mechanism, because all royalties must be voluntary.

Which gets back to the long thread here - it is extremely difficult to put all manner of royalty logic, taxes, splitting, etc. perfectly inside of a smart contract and convince marketplaces to voluntarily use it (see point above - royalty payments must be voluntary!) and foist the gas fees on themselves or on their customers.

Which goes back to my argument - if royalty payments, by definition, must be voluntary, then there is no point putting all of the logic in smart contracts, at least at first. We should make it as seamless as possible to calculate the proper royalty payment and then pay it successfully with full audit capabilities. We then encourage / force all marketplaces to use it to avoid public shaming / blacklisting from NFT creators.

control if and how this can be modified post-minting.

I strongly think that royalties shouldn't be modifiable after mint, or at least not after the first transfer

We should allow royalty changing to be locked forever, and then the NFT minter can opt to lock it post-mint. But not allowing anyone to change this ever is an anti-pattern - we cannot predict every use case. The market can determine what is preferred and the NFT minter decides what is beneficial to them.

You need to remember that not all art is sold. People give it, exchange it, whatever. There is no reason to consider a transfer that didn't result into no royalties to be a "non compliant transfer".

Right - so the RoyaltiesPaid event references a Transfer and says "this is a sale, here are the royalties". All of the other Transfer events are assumed to be 'not sales' until proven otherwise. This is voluntary.

For this reason, there is absolutely no way to force anyone to comply on a Royalty system. If you disallow a marketplace, they create a proxy contract for peanuts and can sell again.

Right - again, royalties must be voluntary and always will be voluntary.

JSON means off-chain, this is not possible. Not everything is centralized, DEX can't follow off-chain. And as seen with Uniswap, things tend to go Dex.

My point is that because it's voluntary, it doesn't matter if it's off-chain. Same with NFT metadata that is off-chain. The Dapp creators choose to use it, voluntarily.

What we need are "NFT Legos" similar to "DeFi Legos" that contracts build on. But calculating and managing royalties off-chain is not a viable solution. The same way it's not viable to consider all transfer are sales.

I don't see why off-chain data used for calculations is not viable any more than off-chain metadata is not viable, as off-chain metadata is used widely.

Also, again, here people focus on "Direct sales royalties". There are a lot more type of royalties than this, we need to address a bigger problem. Added on top of that, rightly pointer by Anthony that there are laws (about paying those royalties but also the amount and value of it.)

We create a JSON spec that has all of these options.

dievardump commented 3 years ago

But not allowing anyone to change this ever is an anti-pattern - we cannot predict every use case.

the amount of royalties on a piece will also determine its value. If I buy something with a 3% royalties, but then it changes to 10% out of the blue just because the minter decided they wished more, my piece lost a lot of value. I wouldn't trust anything like this. Also this complexifise again the system more since dApps will have to check this and show it to the users. More complexity == less adoption

I think you misunderstand me - this event references a prior Transfer event that the royalty-payer is referencing when they pay the royalty. It could optionally be included inside the Transfer transaction itself, by setting txsale to empty.

I would say giving the choice to a user to pay the royalties in the future if they wish is not a viable solution. The Royalties should be voluntary for dApps to follow, not for direct users if the dApp they use decides to adhere to it.

I also think in term of usability and because gas is damn high, there shouldn't have to be another transaction to declare royalties paid, should done in the same transaction as the exchange. Creating a standard that makes everything more expensive (paying for another tx) will make it unused

Voluntary doesn't mean that we shouldn't ease the things so much that any contract can get as much information needed in one function call to another contract; instead of dapps having to fetch metadata -> calculate the royalties -> feed all this to their sale contract which will actually have to verify that the given values were signed by the dApp so it is sure the users aren't trying to bypass royalties, etc...

Also dex should have a mean to follow the royalties, which is not possible with the off-chain way, except if you expect users to pay in the future, which as I said is imo not a solution.

Everything happens in smart contracts, declaring Royalties outside of the chain is for me an anti-pattern, and it will create more problems and edge cases than a definition on-chain, with a simple api, that everyone can build on.

If you look at my message from a few days ago, we're not totally off the same page, we just disagree about the on-chain / off-chain, but next to that, I do think the simplicity of a RoyaltiesReceived (or RoyaltiesPaid or whatever verbose name we want to give it) is the way to go.

bardionson commented 3 years ago

I would hope that we do not need separate royalty contract terms for each country. If we have the industry set the royalty to 10% or higher on NFT this would exceed the legal requirements for royalties in each country. I am new to smart contract code. But if you had to deal with changing regulations regarding any contract would you have oracles that tell you the proper way to compute the value of a transaction like a royalty.

Could the complexity of some of the royalty determinations be held in oracles? I really don't know what I am talking about yet. I will do more research and learn some Solidity

seibelj commented 3 years ago

But not allowing anyone to change this ever is an anti-pattern - we cannot predict every use case.

the amount of royalties on a piece will also determine its value. If I buy something with a 3% royalties, but then it changes to 10% out of the blue just because the minter decided they wished more, my piece lost a lot of value. I wouldn't trust anything like this.

This is a philosophical question, and I agree with you I would not want an NFT where the royalties can change, but the solution to this is to allow locking it on mint. But I don't think we should prevent someone. It doesn't even have to be part of this standard as someone could just add that feature later for their own contract separate from this standard. Not something that could be prevented IMO.

I would say giving the choice to a user to pay the royalties in the future if they wish is not a viable solution. The Royalties should be voluntary for dApps to follow, not for direct users if the dApp they use decides to adhere to it.

I also think in term of usability and because gas is damn high, there shouldn't have to be another transaction to declare royalties paid, should done in the same transaction as the exchange. Creating a standard that makes everything more expensive (paying for another tx) will make it unused

I agree - and this solution would allow it to be included in the same exchange. Would just need to include payment inside of the exchange transaction. Basically what I'm proposing here is 1) And event format we all agree on, and 2) A way to calculate the royalty using data off-chain. Then we write a (say, Javascript) SDK to help Dapp developers use it.

If you look at my message from a few days ago, we're not totally off the same page, we just disagree about the on-chain / off-chain, but next to that, I do think the simplicity of a RoyaltiesReceived (or RoyaltiesPaid or whatever verbose name we want to give it) is the way to go.

I think we are mostly in agreement except on the fundamental issue of off-chain metadata. I am proposing we will never get an adequate solution here, other than a basic "N royalty receivers, N percent for all of them" but that said, we really need to include support for ERC20 in addition to ETH, which adds more complexity.

The JSON format would look something like:

{
    ...
    royalties: {
        // Tokens accepted for payment. If exchange in other token, royalty MUST be paid in these
        tokens_allowed: ["ETH", "USDC", "DAI"],
        // Total percent with 5 decimals. This would be 5%
        total_fee: 500000,
        // the fee percentages for receivers MUST add up to the total_fee
        receivers: {
            {
                address: "0xabc190...",
                // 3.5% of the fee
                fee: 350000
            },
            {
                address: "0x091cba...",
                // 1.5% of the fee
                fee: 150000
            }
        }
    }
}

We could probably encode just this use-case above in a smart contract format, that is optionally called on transferFrom, by creating a new ERC721 extension that includes transferFromWithRoyalty. Then we need logic that handles

Anything beyond the simple-fee model above would be out of scope.

jamesmorgan commented 3 years ago

Some great chats on this subject, thanks for getting involved everyone.

I am not in favour of moving royalty payments off-chain, I believe we need a solution similar to the "Money Legos" which allows marketplaces to query and fulfill the royalty amounts for tokens sold on their platform, moving off-chain break this link and makes the payout centrlaised and less lego like and more traditional banking/settlement like.

Ideally I still believe the standard needs to be:

I still favour a simple interface like this

 function royaltyInfo(uint256 _tokenId) returns (address receiver, uint256 amount)

Leaving the underlying implementation of what this does down to the NFT contract itself.

I am also not 100% onboard the callback and event method mention at the root of this EIP but not sure of many alternatives to this atm but happy for ideas around this if anyone has any?

I dont think the simple interface prevents any payments of royalties in ERC20 or vanilla ETH as the caller simply gets the recipient address and amount meaning they can send any time of earnings to it. The only caveat is that the recipient of the royalties needs to know how to handle properly which also isn't that difficult to build, unless I am not thinking it through enough.

Remscar commented 3 years ago

I was going to suggest having multiple royalty addresses (incase royalties are to be split amongst multiple addresses) but I think that should be up to the recipient on how to handle and the standard should be as minimal as possible.

Thanks for filing this standard, I think it's great and I support it.

Lenoftawa commented 3 years ago

I have been watching this for a while. Thanks @VexyCats I'd really like to see this work. I find myself in agreement with a lot of what @jamesmorgan and @dievardump have said. However, I have come at this from a different angle and I'm still very light on Solidity experience so I may be way off target:

nullren commented 3 years ago

i'm glad i've found this thread because i've been thinking about this for the past several days.

  • I do believe that the standard should have a means of enforcing the royalty (even if it is game-theoretical or social in some way). But that means we need to account for peer-to-peer transactions. I don't see why this should be limited to exchanges.
  • I can't see how this could be done without the payment passing though the NFT, subtracting the fee as part of the transfer of ownership and leaving the record of the amount paid. I don't see why an exchange needs to pay the royalty, although it should have access to it. This still allows 2 parties to make a secret payment that avoids the fee, but if transfer can only happen on payment, then avoiding the fee reduces the publicly recorded transaction amount and potentially reduces the perceived value of the item.

these are exactly the concerns i've been thinking about with respect to NFTs and royalties. ERC721 really only cares about ownership and uniqueness. some exchanges can choose to participate in "royalty-awareness", but it's too easy to bypass that without something added to ERC721 that enforces this some how.

one of the ideas i was playing with was restricting what addresses could be approvers for a token and baking that into my NFT contract. so trying to approve an exchange that was not added to the allow-list of approvers would fail. it's not quiet ERC721 but helps give the creator some control.

dievardump commented 3 years ago

one of the ideas i was playing with was restricting what addresses could be approvers for a token and baking that into my NFT contract. so trying to approve an exchange that was not added to the allow-list of approvers would fail. it's not quiet ERC721 but helps give the creator some control.

You can't know if a transfer is a sale or a gift or just a transfer between two accounts of the same person.

We can not enforce Royalties in this way. At least not Royalties on direct sales, because you can't know if it's a sale. Royalties like "harbergex tax" can be.

oscarsernarosero commented 3 years ago

This might be a dumb idea, or maybe already discussed, but what if we just add a layer to interact with the 721 standard to make sure we handle the royalties? For example, we can set some methods like setPrice(), buy(), and isOnSale() on top of the royalty specification like the ones setup by EIP 2981. In this way, we can override the transfer methods to be callable only by the buy() method and also making sure that royalties are transferred as specified in the EIP2981 in the NFT minting process.

Let me specify these methods just in case this can actually make sense to some of you:

 /**
    *@dev The owner of the NFT can set the price of sale here
    *
    *@dev The value passed as price must be saved in a public uint variable to ensure that
    *it would be accessible from outside.
    *
    *
    *@param price the price in ETH of the NFT (wei);
    */
   function setPrice (uint price) public onlyNFTOwner;

 /**
    *@dev this is just enforce that the price is public
   */
   function getPrice () public view returns (uint price);

 /**
    *@dev The owner can decide not to sell the NFT by passing false to this method
    *
    *@dev The value passed must be saved in a public bool variable to ensure that
    *it would be accessible from outside.
    *
    *@param onSale bool that represents the willingness of the owner to sell the NFT
    */
    function setOnSale(bool onSale) public onlyNFTOwner;

 /**
    *@dev this is just enforce that onSale is public
    */
    function isOnSale() public view returns (bool onSale);

 /**
    *@dev This is an internal method called at the moment of a sale.
    *
    *@devThis method calculates the royalties depending on the sale price. 
    *
    *@dev This method also pays to the addresses that are entitled to royalties (SmartContracts 
    *if there was a collaboration of artists) (This is also worth another EIP)
    *
    *@dev This method should call getPrice() internally to calculate royalties
    */
   function payRoyalties() internal returns (bool success); 

 /**
    *@dev This is the method through which people can buy an NFT
    *
    *@param nft_id the id of the NFT that the person is trying to purchase
    */
     function buy (uint nft_id) public payable returns (bool success){
          require(isOnSale(), "Not on sale");
          require(msg.value == getPrice(), "Wrong amount of Eth");
          payRoyalties(getPrice());
          payable(NFT_owner).transfer(getPrice() - getRoyalties(getPrice()));
          transfer(NFT_owner, msg.sender, nft_id);
    }