ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.88k stars 5.27k 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);
    }
}
jamesmorgan commented 3 years ago

@oscarsernarosero thats probably kind of like what a marketplace contract would look like anyway I think.

Also would require an approval from the owner to allow the transfer.

oscarsernarosero commented 3 years ago

thats probably king of like what a marketplace contract would look like anyway I think.

@jamesmorgan Yeah. That's the main idea. To incorporate the functionality of a market place into the NFT itself.

Also would require an approval from the owner to allow the transfer.

Not really. Since it doesn't use the safeTeansferFrom() method of ERC721. Instead it uses the simple transfer() method since we do our own checks in the buy() method. These checks being if the amount of Ether is the same as the price, and if it is for sale. Actually, this would also save money on gas fees since we don't have to approve anything.

However, in this way there would be little incentives for marketplaces to post these NFTs since people could bypass the marketplace and simply buy directly through the NFT contract's buy() method without paying any fees to the market place. So, I was thinking maybe to create another method to allow sales only through an address or list of addresses function onlySellableThrough(address marketPlace) public onlyNFTOwner returns(bool success);

This way, the market place could simply call getPrice() and add a percentage to it which would be the listing price for the NFT.

The minting method should be also overwritten to accept the info of the royalties.

dievardump commented 3 years ago

@oscarsernarosero you are trying to turn the NFT contract into a market contract, when the space is especially trying to separate those.

What we are trying to do here, is to add a specification to the (already heavy) ERC721 (and 1155). This means we wish to had as little code as possible to NFT contracts. You are adding 6 functions, most of which are functions that are supposed to be in "Sale" contracts. If an NFT is on sale or not, or its price, shouldn't be set in NFT contract, at least people try to not do that anymore since a few years now.

arpu commented 3 years ago

@dievardump can you give me some hints on how i should implement is on sale or not, or its price,... ? more in a central database?

oscarsernarosero commented 3 years ago

@dievardump I'm sorry, but what you are saying is total contradiction. Royalties are entirely a market concept. There is no way to enforce the fair payment of royalties to the artists without being aware of the price of sale.

On the other hand, I don't think ERC721 standard is a heavy standard, plus there are implementations audited and ready to use out there for you.

oscarsernarosero commented 3 years ago

@arpu You would simply have to connect to the blockchain (either through a node or a block explorer) and ask for the value of the variables to the NFT contract since these values are going to be stored in there.

dievardump commented 3 years ago

@dievardump I'm sorry, but what you are saying is total contradiction. Royalties are entirely a market concept. There is no way to enforce the fair payment of royalties to the artists without being aware of the price of sale.

You misunderstand the idea here. The idea is to have somewhere, stored and accessible to other contracts, the amount of royalties the creator want to receive for their NFT. What we try to do here, is make a standard so all Marketplaces can determine how much royalties have to be sent, and where. -> Only the Marketplace -OpenSea, Rarible, ...- knows the price of the item, the NFT contract doesn't know about the NFT price, since it was set on the Marketplace, and only for this Marketplace, most of the time with a signed order; NFT contracts do NFT stuff: mint, transfer, burn; Marketplace contract do market stuff: auction, order, buy, sell; separation of concerns/functionalities; what we want to do here is making easy and cheap for Marketplaces to accept to check if there are royalties defined for this specific NFT

I don't think ERC721 standard is a heavy standard

The contract itself is heavy. It's costly and already "big" for the EVM. Which means adding the strict minimum functions/weight to it is of high importance.

@arpu this is not the place for this kind of requests. Please stay focus on the Royalties thing

oscarsernarosero commented 3 years ago

@dievardump ok I see your point. However, the royalties info that is being discussed adds up weight on the NFT contract as well. So, it is a problem that would come with any kind of proposal no matter what. Therefore is not a problem of my approach, but a problem with probably all approaches to the problem.

I agree with your vision of keeping separate market activities and NFT activities, but until what point is this practical and realistic? In a free market, such as the Ethereum ecosystem, people produce outputs for an economical incentive. The market value is intrinsic to the process of creation of an NFT. Therefore, it is logical that the value of royalties and price can be set as part of the NFT.

I do see your point tho. I just think we should be more practical than philosophical.

dievardump commented 3 years ago

@dievardump ok I see your point. However, the royalties info that is being discussed adds up weight on the NFT contract as well. So, it is a problem that would come with any kind of proposal no matter what. Therefore is not a problem of my approach, but a problem with probably all approaches to the problem.

Which is why we need to add the strict minimum, not nothing. One, maximum two functions, maybe one event. Your approach however adds a lot and go at counter currant than everything that is being done to build separate "blocks" instead of "all in one place".

I do see your point tho. I just think we should be more practical than philosophical.

But this is what is already done everywhere. NFT contracts and Market contracts are created separated since a lot of time. Most marketplaces do that separately, this makes it a lot easier to upgrade things and to separate concern (one bug in a contract doesn't affect the others). Look at Rarible, Opensea and co contracts. All nicely separated.

arpu commented 3 years ago

@dievardump sorry not focus on Royalties, i need to understand how i could implement the market contracts separated to my NFT Contract ( https://etherscan.io/address/0x439596812256572f6edd25d7a9a0b6588dd91227) can you give me some hints?

blmalone commented 3 years ago

Hi folks, Nice work so far! I like the current direction that this EIP going.

I'm keen to unpack the current problem around only supporting a single royalty receiver and amount. After reading this thread, it seems like NFT collaborations are quite common and something that should be supported.

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

AFAIK, there are many ways that this could be approached. However, it is important to keep in mind that we want to make this easy for marketplaces to support.

Potential Scenario: If we had NFT X created by Alice and Bob. It was decided that Alice gets 60% of the royalty amount and Bob gets 40%. Together, they request that they receive a 0.1% fee.

You'll notice now that the amount of data that is necessary for a marketplace to fulfill the royalty request just grew much larger (Alice's Address, Bob's Address, Alice's Portion of the Fee, Bob's Portion of the Fee, Fee).

Though this can be condensed to: (Alice's Address, Bob's Address, Alice's Fee, Bob's Fee).

The current implementation could support a feature like this, but it would require the marketplaces to store extra data about the royalty allocations off chain. This could get confusing and lead to problems. Having a single address may mean that the collaborators would need a multisig wallet. This feels like too much overhead.

Perhaps I've missed the point where you've discussed supporting more data as parameters to the Royalties constructor?

This is currently the suggested constructor.

Royalties(royalty_amount, msg.sender)
bardionson commented 3 years ago

I am just learning how this works. I really need to find a good Ethereum Programming for Dummies tutorial. So forgive my uninformed ideas. I only know of a few marketplaces that have collaboration split payment support. It might be acceptable if an NFT moves out of a specific market to an open market that the royalty be passed to the creator with the largest share of the cut or perhaps the primary address that minted the NFT. I am afraid that if the marketplace goes out of business the contract might lose access to that off chain extra data. I have heard of the concept of an oracle. Could there be a decentralized royalties oracle(s) that would take very small fee or cut of the royalty for maintaining the rules, percentages, artists addresses for the royalties. The markets would send the data to the oracle and it would maintain the lookups. Is this how oracles work?

bardionson commented 3 years ago

Does this royalty system proposed here bear any resemblance to EIP-2665?

This article says https://medium.com/cryptograph/unlocking-a-digital-ownership-revolution-9f564ca07fe3 "Cryptographs are the first NFTs to use the ERC-2665 implementation, with the transfer fee of a Cryptograph being calculated as a percentage of its last sale price, transparently and on-chain. Each Cryptograph (like any ERC-2665 token) is still a fully-fledged ERC-721 token: you can design operators, approve an address, and use various types of transfer functions (unsafe, safe, safe with data overloading)."

nullren commented 3 years ago

I'm keen to unpack the current problem around only supporting a single royalty receiver and amount. After reading this thread, it seems like NFT collaborations are quite common and something that should be supported.

@blmalone all the royalty splitting logic can (and should) be handled by a separate smart contract/wallet that both Alice and Bob can access (eg, multisig wallets are an example of this). primary reason why you don't want to include that here is you're forcing a pretty hard-to-do implementation on everyone else and risk Alice and Bob to losing money due to a bad implementation somewhere. whereas they can both set up a wallet/contract they both trust and have tested and use that everywhere.

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.

it's definitely a sledge-hammer solution. and yes, it wouldn't stop direct transfers (eg, under the table transactions can still happen).

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.

@dievardump could we enforce them at all without a tax scheme? doesn't seem so... but ¯\_(ツ)_/¯

jamesmorgan commented 3 years ago

The latest changes all look good to me, I have incorporated them into our current code base and all is working as expected.

Does anyone know what or how to move this EIP on from a draft state?

Secondly does anyone have any thoughts on why this should not be moved on from draft to final?

seibelj commented 3 years ago

The latest changes all look good to me, I have incorporated them into our current code base and all is working as expected.

Thanks - I incorporated allowing the payer to use both ETH and any ERC20. I also updated the ERC165 calculation, and made a function to call the event as you can't call it directly. Will you publish any code of your implementation? I think a reference implementation would be helpful.

Does anyone know what or how to move this EIP on from a draft state?

Secondly does anyone have any thoughts on why this should not be moved on from draft to final?

Let me go over it with a fine-toothed comb again, last time I found typos / bugs. Would be beneficial if many people looked it over closely.

blmalone commented 3 years ago

whereas they can both set up a wallet/contract they both trust and have tested and use that everywhere.

Sounds good. Best not to over complicate that side then.

@jamesmorgan I'll go over the EIP again.

dievardump commented 3 years ago

Secondly does anyone have any thoughts on why this should not be moved on from draft to final?

I do think that having MarketPlaces emit RoyaltiesReceived isn't a valid behavior.

There can be hundreds of Marketplaces, those Royalties can be delivered by any contracts, however the contract that declares the royalties is the NFT contract, it shouldn't be to another contract to emit an event.

1/ The Royalties recipients shouldn't have to look everywhere to follow royalties. One place should hold all events concerning their royalties for a specific id / registry. 2/ The NFT emitting platform shouldn't have to look everywhere to be able to give data about royalties to their users ^ This will complicate the way platform allowing user to mint, will be able to gather information to give them statistics about their sales, royalties received etc... I for one am not sure that I would follow this EIP, since it would force me to track all transactions received by my users, to find if there are logs of royalties associated to it.

In the worst case where this behavior is kept, at least not an event with "Received" since the Marketplace is the one emitting the royalty part, not receiving.

I also think that the marketplace shouldn't be aware of where the royalties are going. But if it saves a call then for the sake of gas, why not.

I also do not understand the need of scaling factor of 10,000; 2 points after , should be more than enough for this use.

seibelj commented 3 years ago

There can be hundreds of Marketplaces, those Royalties can be delivered by any contracts, however the contract that declares the royalties is the NFT contract, it shouldn't be to another contract to emit an event.

The NFT contract emits the event. The caller just has to notify the contract that it was paid, as the royalty is optional, as has been discussed ad nauseum throughout this post. Watchers look at the NFT contract itself only, just as they do for all other NFT events.

dievardump commented 3 years ago

The NFT contract emits the event.

From EIP:

Event ReceivedRoyalties to be used by marketplaces when transferring royalty payments

Watchers look at the NFT contract itself only, just as they do for all other NFT events.

Thats why I say I'm not really ok with this event being emitted by marketplaces, as stated here: https://github.com/VexyCats/EIPs/blob/master/EIPS/eip-2981.md


If this is not the last version, what is then? Because both the versions in the first post do not take the tokenId as parameter for royaltyInfo, so those are sure outdated. And they contain quite a few functions when 2 are needed from all the talk we had here (getting royalty info, sending royalties).

Also if we go with a royaltyReceived, then we might want to follow what already happens in the ERC with a "onRoyaltyReceived()" that returns a bytes4


seibelj commented 3 years ago

The NFT contract emits the event.

From EIP:

Event ReceivedRoyalties to be used by marketplaces when transferring royalty payments

Watchers look at the NFT contract itself only, just as they do for all other NFT events.

Thats why I say I'm not really ok with this event being emitted by marketplaces, as stated here: https://github.com/VexyCats/EIPs/blob/master/EIPS/eip-2981.md

The marketplace has the functionality to execute a transfer (as part of a sale, auction, etc.). If they support the royalty payment standard, they pay the royalty recipient and call receivedRoyalties() on the same NFT contract which they call transferFrom() on. It is the simplest possible way - I cannot think of anything simpler.

If this is not the last version, what is then? Because both the versions in the first post do not take the tokenId as parameter for royaltyInfo, so those are sure outdated. And they contain quite a few functions when 2 are needed from all the talk we had here (getting royalty info, sending royalties).

Also if we go with a royaltyReceived, then we might want to follow what already happens in the ERC with a "onRoyaltyReceived()" that returns a bytes4

You need to look at latest version of the EIP https://eips.ethereum.org/EIPS/eip-2981 The functions take tokenId now royaltyInfo(uint256 _tokenId)

dievardump commented 3 years ago

You need to look at latest version of the EIP https://eips.ethereum.org/EIPS/eip-2981 The functions take tokenId now royaltyInfo(uint256 _tokenId)

This version is linked nowhere nor here nor on the original: https://github.com/VexyCats/EIPs/blob/master/EIPS/eip-2981.md

I would have expected them to be synchronized, sorry about this, I couldn't understand what was "done" in what we have here or on the first EIP file.

The marketplace has the functionality to execute a transfer (as part of a sale, auction, etc.). If they support the royalty payment standard, they pay the royalty recipient and call receivedRoyalties() on the same NFT contract which they call transferFrom() on. It is the simplest possible way - I cannot think of anything simpler.

Yes, I'm very for calling a function, and can't think about anything simpler neither , that's why I wrote this in september on my first post here and advocated for since the beginning. What I mean is the ERC standards, today, use the "on{Action}" pattern when calling a function like this, and we might want to do the same thing because: 1/ The on{Action} implicitly declares this as a "callback", this is better from the point of vue of code reading / understanding / semantic(?) 2/ returning the bytes4 selector of the function is a good practice to ensure that the royalties have been received well

The idea is just to change the name of receivedRoyalties by onRoyaltiesReceived (or onReceivedRoyalties) and return a bytes4 this.onRoyaltiesReceived.selector

Coming back to the royalty subject: I however think it makes little good to call the callback and emit an event if the contract can't verify that the royalties have actually been sent. (i.e: royalties sent with the call to the callback, who does whatever it has to do with he amount)

Allowing a standard to emit an Event that claims that royalties have been paid, without a strict verification that it has actually been done, seem broken

Nokhal commented 3 years ago

I do not see any difference between this EIP and a donation button.

Nothing is enforced trustlessly, it's all a "nice gesture" from the marketplace and you have to take it's word from it. Imagine optional gas fees...

Suggestion

It should all be metadata inside the ERC-721 offchain descriptor, as it would be just as not secure and not trustless from both dev and user perspective, while more gas efficient, and allowing retrocompatibility with all existing NFT without having to upgrade their smart contracts. Also it would support way more currency than just ETH and USDC.

You have to keep in mind that emitting those events is gonna cost around 5USD with current gas/ETH prices, so there need to be a valid reason to take 5USD out of the royalty pot. In the above proposal, I do not see it as none of the decentralized trustlesness that the EVM provide is being leveraged. Do the same thing offchain, it's cheaper.


competing standard :

2665

^ Only allow the marketplace to pay a % of sale fee if the market place is trusted to be honest by the royalty recipient. Flat fee otherwise, for any transfer.

jamesmorgan commented 3 years ago

The idea is just to change the name of receivedRoyalties by onRoyaltiesReceived (or onReceivedRoyalties) and return a bytes4 this.onRoyaltiesReceived.selector

This change also aligns with the existing ERC721 callback event onERC721Received - this makes sense to me for a minor alteration. @dievardump

Coming back to the royalty subject: I however think it makes little good to call the callback and emit an event if the contract can't verify that the royalties have actually been sent. (i.e: royalties sent with the call to the callback, who does whatever it has to do with he amount)

  • we can send value with the callback
  • we can use "approve" before calling with any other ERC

Allowing a standard to emit an Event that claims that royalties have been paid, without a strict verification that it has actually been done, seem broken

I think I agree with this, it seems a gesture and more for reporting/auditing purposes, but this still needs to be verified again in a post process to check validity. The GAS fee to needless do this provides a disincentive but also doesn't prevent its abuse.

What are the alternatives to this, maybe this event could become OPTIONAL and detailed with the caveats on the EIP?

Or even dropped completely?

What are anyones thoughts on this?


This isnt quite true in my opinion @Nokhal

I do not see any difference between this EIP and a donation button.

Nothing is enforced trustlessly, it's all a "nice gesture" from the marketplace and you have to take it's word from it.

Although nothing is enforced "trustlessly" as you say, this EIP does provide a simple, standardised way to allowed 3rd party marketplaces to request and then apply royalties of the creator(s).

It also plugs an important gap in the existing ERC721 spec without introducing any changes on the widely adopted and a foundational building block of the NFT industry.

My biggest concern with #2665 is that it would reduce the portability of NFTs and puts additional friction to the movements of any assets but I will read the IP in more detail to familiarize myself with it further.

AFDudley commented 3 years ago

If someone else mentioned this I apologize, there are just too many comments here, but I find

This extension provides even more flexibility to the ERC-721 specification. It is possible to set a royalty amount that can be paid to the creator on any marketplace that implements this ERC. If a marketplace chooses not to implement this ERC, then of course no funds are paid for secondary sales.

problematic because it conflates the marketplace or auction system with the entity that created the token by calling safeTransferFrom. This obviously isn't the case with current marketplaces. I'm writing here instead of submitting a PR because I'm not entirely sure what the intent of the sentence is, nor am I entirely sure that I'm understanding it correctly.

Is there a TL;DR for why safeTransferFrom wasn't modified instead of trusting the marketplace to implement the ERC?

EDIT: I know the answer to my question and now better understand what https://github.com/ethereum/EIPs/issues/2571 is trying to accomplish. I think the solution to these problems is some kind of wrapper, a vault/proxy for ERC721s and ERC1155s, but that seems quite complex since all the TokenIDs would be wrapped together...

dievardump commented 3 years ago

Is there a TL;DR for why safeTransferFrom wasn't modified instead of trusting the marketplace to implement the ERC?

Because (1) not all transfers are sales. (2) The value can be exchanged in another transaction than the transfer itself (3) There is no way to force marketplaces to follow something, they will follow if it makes sense, is easy and is not broken (4) it would break existing systems in place to force something in safeTransferFrom if they can't be updated

A standard is not an obligation. Users will then from themselves go to the platforms that follow the standard if it's done right.

seibelj commented 3 years ago

https://eips.ethereum.org/EIPS/eip-2981

Please see latest. Updated the function to onRoyaltiesReceived() and made content edits. I'll get this moving out of DRAFT if no one has objections.

The updated abstract:

This standard extends the ERC-721 specification to enable setting a royalty amount paid to the NFT creator or rights holder every time an NFT is sold and re-sold. This is intended for NFT marketplaces that want to support the ongoing funding of artists and other NFT creators. The royalty payment must be voluntary as required by the EIP-721 standard, as transferFrom() includes NFT transfers between wallets, and executing transferFrom() does not always imply a sale occurred. Marketplaces and individuals implement this standard by retrieving the royalty payment information with royaltyInfo(), paying the proper royalty amount to the royalty recipient address, and calling onRoyaltiesReceived() to notify the NFT contract of the royalty payment. Payments are simple and sent only to a single address. This ERC should be considered a minimal, gas-efficient building block for further innovation in NFT royalty payments.

Final rationale on optional / voluntary payments:

Optional royalty payments It is impossible to know which NFT transfers are the result of sales, and which are merely wallets moving or consolidating their NFTs. Therefore, we cannot force every transferFrom() call to involve a royalty payment, as not every transfer is a sale that would require such payment. We believe the NFT marketplace ecosystem will voluntarily implement this royalty payment standard to provide ongoing funding for artists and other creators, and NFT buyers will assess the royalty payment percentage as a factor when making NFT purchasing decisions.

Final rationale on simple payments:

Simple royalty payments to a single address It is impossible to fully know and efficiently implement all possible types of royalty payments and logic, so it is on the royalty payment receiver to implement all additional complexity and logic for fee splitting, multiple receivers, taxes, accounting, etc. in their own receiving contract or off-chain. If we attempted to do this as part of this standard, it would dramatically increase the implementation complexity, increase gas costs, and could not possibly cover every potential use-case. Therefore, implementers desiring more complex royalty payment logic should create their own smart contract that implements their own requirements upon the receipt of ETH or ERC20 tokens and / or the execution of onRoyaltiesReceived(). This ERC should be considered a minimal, gas-efficient building block for further innovation in NFT royalty payments.

Please read the EIP closely.

wighawag commented 3 years ago

Hey all, Here are some comments I have after reading the latest EIP draft :

I have also a more general comment on the EIP:

It has the issue that there is no guarantee the call to it will actually be accurate. As such it won't be useful for tracking contrary to what is stated in the EIP.

If we really want to tackle the problem somehow, it should be a different EIP so EIP-2981 can focus on what matter the most : the ability for a NFT to make the expected royalty known.

A new EIP could even use a different mechanism. For example a registry contract could be a better approach so that a log query could give us all royalty received across all ERC721 (or ERC1155) tokens for a particular creator, buyer, etc... It could also process the payment so it can at least verify that payment was made to the recipient. And by being a separate registry, this would not complexify each ERC721/ERC1155 token implementation. All of that could be discussed in that separate EIP.

dievardump commented 3 years ago
* We should split `royaltyInfo` into `creatorOf` and `royaltyOf`. this could even be 2 separate EIPs. the `creatorOf` is on its own already something many platform are waiting to be standardized. This might make the EIPs easier to get finalized too as each become simpler.

Royalties recipients are not always creators. I don't think creatorOf should be introduced in something linked to Royalties

If we really want to tackle the problem somehow, it should be a different EIP so EIP-2981 can focus on what matter the most : the ability for a NFT to make the expected royalty known.

A new EIP could even use a different mechanism. For example a registry contract could be a better approach so that a log query could give us all royalty received across all ERC721 (or ERC1155) tokens for a particular creator, buyer, etc... It could also process the payment so it can at least verify that payment was made to the recipient. And by being a separate registry, this would not complexify each ERC721/ERC1155 token implementation. All of that could be discussed in that separate EIP.

Yes that's what I think is the best too. This EIP as it's created now, not verifying the amount of royalties sent is for me not really usable. I sent you some screenshot on Discord about something we might work on with a DAO, that include a Royalty Registry à la ENS.

seibelj commented 3 years ago

Hey all, Here are some comments I have after reading the latest EIP draft :

  • The EIP mention the minimum amount to be 1. It should state that zero is allowed.

Totally agree, will fix

  • We should split royaltyInfo into creatorOf and royaltyAmountOf. this could even be 2 separate EIPs. the creatorOf is on its own already something many platform are waiting to be standardized. This might make the EIPs easier to get finalized too as each become simpler.

Disagree - this EIP says how much to pay (percentage), and where. If the implementer wants to do more advanced splitting, this goes in a separate contract.

  • The event metadata would be more flexible if it was bytes instead Also naming it "metadata" in the context of erc721 is confusing. I would thus make bytes data instead.

Agree, will change

  • Purchase can happen in batch, and having to call the onRoyaltiesReceived for every one seems not very efficient. We could maybe make it accept an array instead
  • onRoyaltiesReceived is also not really compatible with ERC1155, an amount params would make it so
  • On that note, the EIP should be made more generic and not tied to ERC721. I do not see any intrinsic incompatibility with ERC1155 and it would be a shame that royalty would not work there.

I'm OK with making tokenId an array and adding the values array to support ERC1155. ERC1155 only supports batch transfers to and from single addresses, so it should be fine to support.

I will modify this to also have verbiage for 1155 support even though this is primarily focused on 721.

I have also a more general comment on the EIP:

  • We should not encumber this EIP with onRoyaltiesReceived and the corresponding event.

It has the issue that there is no guarantee the call to it will actually be accurate. As such it won't be useful for tracking contrary to what is stated in the EIP.

If we really want to tackle the problem somehow, it should be a different EIP so EIP-2981 can focus on what matter the most : the ability for a NFT to make the expected royalty known.

I agree and disagree with your logic 😄 We already accept that you can pay later, in batches, etc. as paying royalties is voluntary. You ultimately never have to use onRoyaltiesReceived(). But we have discussed this at length so much for months, and nothing prevents a royalties payer from using royaltyInfo() and then a different standard to pay later. Furthermore, this will support 721 and 1155 (after a few modifications), and have an arbitrary data field, which will be simple enough for the 99% case and be forward-compatible. I fear if we don't include this here, it will be months (or over a year?) before another standard is finalized to handle it. We can move the NFT industry forwards now and benefit the ecosystem greatly.

A new EIP could even use a different mechanism. For example a registry contract could be a better approach so that a log query could give us all royalty received across all ERC721 (or ERC1155) tokens for a particular creator, buyer, etc... It could also process the payment so it can at least verify that payment was made to the recipient. And by being a separate registry, this would not complexify each ERC721/ERC1155 token implementation. All of that could be discussed in that separate EIP.

This shall not prevent any further innovation such as this.

Nokhal commented 3 years ago

Agreed on royaltyInfo() being a feature that most marketplace would like to use.
I do not believe in opt-in on chain-royalties though. On-chain code is law, with the EVM being the executioner and the network the jury. If it's opt-in, cheaper to do it off-chain as a metadata extension.

seibelj commented 3 years ago

I do not believe in opt-in on chain-royalties though. On-chain code is law, with the EVM being the executioner and the network the jury. If it's opt-in, cheaper to do it off-chain as a metadata extension.

Again, it is impossible to make it required in the current ERC721 and ERC1155 standards as has been reiterated exhaustively in multiple ways. Let me quote myself:

It is impossible to know which NFT transfers are the result of sales, and which are merely wallets moving or consolidating their NFTs. Therefore, we cannot force every transferFrom() call to involve a royalty payment, as not every transfer is a sale that would require such payment. We believe the NFT marketplace ecosystem will voluntarily implement this royalty payment standard to provide ongoing funding for artists and other creators, and NFT buyers will assess the royalty payment percentage as a factor when making NFT purchasing decisions.

Feel free to create your new EIP, but this one is voluntary, and doesn't prevent you from swapping in your own way to pay royalties later.

wighawag commented 3 years ago

@seibelj

You ultimately never have to use onRoyaltiesReceived(). But we have discussed this at length so much for months, and nothing prevents a royalties payer from using royaltyInfo() and then a different standard to pay later. Furthermore, this will support 721 and 1155 (after a few modifications), and have an arbitrary data field, which will be simple enough for the 99% case and be forward-compatible. I fear if we don't include this here, it will be months (or over a year?) before another standard is finalized to handle it. We can move the NFT industry forwards now and benefit the ecosystem greatly.

I am not sure I understand your focus on extra data field. My issues is that onRoyaltiesReceived can be called by anyone with wrong data. and so the event is meaningless. What would be the purpose of an API that can produce invalid values ?

My main suggestion is to carry that idea (having royalty event) as a new EIP (and open the discussion to alternative mechanism which does not require it to be part of the token contract code for example). The fact that it has been discussed at length should not make us accept it for that single reason. Actually it could well be a sign that it should not be part of the standard.

Removing will make the standard simpler and should make it faster to get finalized. Sound like it is also what you want, no ?

wighawag commented 3 years ago

Re splitting royalty info into 2

I agree with @dievardump that creator is probably not the best term and could be left as another standard

Regarding royaltyOwner being split from royaltyAmount has some interesting property that fit well with this concern. It might indeed make sense for royaltyOwner to be able to manage their royalty ownership. This potentially bring new function

Following the pattern of https://eips.ethereum.org/EIPS/eip-173 we could have :

event RoyaltyOwnershipTransferred(uint256 indexed id, address indexed previousOwner, address indexed newOwner);
function royaltyOwnerOf(uint256 id) view external returns(address);
function transferRoyaltyOwnership(uint256 id, address _newOwner) external;  

This complexify the EIP a bit but it could be worth it.

Note though that I do not have strong feeling about the splitting part and even if we added these function, we could skip royaltyOwnerOf and simply use royaltyInfo for getting the data.

seibelj commented 3 years ago

I am not sure I understand your focus on extra data field. My issues is that onRoyaltiesReceived can be called by anyone with wrong data. and so the event is meaningless. What would be the purpose of an API that can produce invalid values ?

The caller may include the event within the same transaction in which the user is paid. Someone who calls this arbitrarily is paying gas costs, doesn't cost the royalty receiver anything, and won't line up with any payment. Even if called outside of the transaction where the royalties are paid, there should be a matching payment with the same token and amount referenced.

Furthermore, the extra data field will allow additional EIPs to solve issues like you bring up. The transaction hash of the payment could be put in the data field, to make detecting spam calls even easier to remove. Or it could store the URI of a JSON blob hosted on IPFS that contains a breakdown of all transactions the payment is referencing.

That is the purpose of the data field - future proofing the EIP by allowing more innovation later.

In regards to tracking the changing of royalty ownership, there could be 1 million tokenIds and a single royalty recipient for the entire contract. Surely we don't want 1 million events to track every tokenId. Or it could be a separate royalty recipient for every tokenId. The point is, we don't want to tell people who to manage that.

The biggest risk is you call royaltyInfo to get the address, and it changes since between the time you fetch it and when you pay. You could also call royaltyInfo() to retrieve the payer address in the same transaction you pay the fee, if you are concerned about this. But it is unlikely to me that this changes often enough to necessitate the complexity of event tracking.

Overall, I am still in favor of the onRoyaltiesReceived() event. It is minimalistic and covers 99% case, and doesn't stop you from using a different mechanism later, and moves the NFT ecosystem forwards. It allows a simple, standard way for the recipient to track all the times they were paid within a single place, rather than having some sort of "sends me the transactions at this email address" anti-pattern.

wighawag commented 3 years ago

Re changing of royalty ownership, you are right that function transferRoyaltyOwnership(uint256 id, address _newOwner) external; might not be enough Add function transferRoyaltyOwnership(address _newOwner) external; and you get what I mean. The point is not to tell how to manage it, but to give royaltyOwner the mean to do so. If there is no standard, we are making it harder. This could be a separate EIP though and as I mentioned there is value in making an EIP simpler. So I agree to leave as is.

Re onRoyaltiesReceived() I still strongly disagree of its presence in this EIP.

Thank for your comment I understand better why you mention the data parameter: to solve the issue onRoyaltiesReceived introduces

I think it is far better to remove the problem in the first place: no onRoyaltiesReceived call and no event.

Regarding the gas cost, I think this is a very weak argument. it is not because it is expensive (and in some chain it will be cheap) that nobody will do it.

The reason we want to track royalty event, is that we consider them valuable and this could generate incentive to fake it.

Note though that my rejection of the current proposal is not a rejection of the ability to track. Just that it will be better handled in a separate EIP

Actually with a registry contract (one potential idea for a new EIP), we get several benefit

seibelj commented 3 years ago

Regarding the gas cost, I think this is a very weak argument. it is not because it is expensive (and in some chain it will be cheap) that nobody will do it.

The reason we want to track royalty event, is that we consider them valuable and this could generate incentive to fake it.

I just don't see the spam argument as problematic. The spammer would pay all the costs, inconveniencing the royalty recipient only in having to filter out events that are irrelevant.

I'm all for additional EIPs, and I am happy to say the onRoyaltiesReceived() function and associated event are but one (of potentially many) mechanisms to handle royalty payment tracking. By supporting EIP2981 you promise that you have the royaltyInfo() and onRoyaltiesReceived() functions, but if a further EIP came along it can borrow the royaltyInfo() function and implement their own payment tracking mechanism, or layer in their own mechanism in addition to this and let the payer select.

But I know how these EIPs go, and what you are proposing will take a very long time to get consensus on. This solution here will cover 99% case, and move the industry forward ASAP, which is what NFTs sorely need.

wighawag commented 3 years ago

But I know how these EIPs go, and what you are proposing will take a very long time to get consensus on. This solution here will cover 99% case, and move the industry forward ASAP, which is what NFTs sorely need.

What I am proposing should actually make this EIP final faster, as I don't think anybody have blocking issues in regard to royaltyInfo. We could have that agreed first and move on.

The issue is about onRoyaltiesReceived which actually fails to fulfil what is actually needed, a reliable event stream for royalty payment.

inconveniencing the royalty recipient only in having to filter out events that are irrelevant.

This is not only about the recipient, but every consumer of the stream

The issue is that this EIP do not propose a complete and reliable mechanism to filter out these wrong events and thus fails to fulfil its goal. Arguing that we can come up with scheme later to solve the problem indicate to me that this should be better tackled in a different EIP. Plus as mentioned there could well be better mechanism for it.

NHQ commented 3 years ago

Sorry to take this is a new direction, but I think I solved the puzzle. NFTs need support for licensing. Payment upon sale of the original is a subset of conditions for royalties. The NFT buyer should be agreeing to a license deal, which pays a royalty not just for re-sale, but for any use (per terms). For instance, the news channel wants to show nyanCat.gif, they buy a license, which triggers a royalty to the creator. And then often, there are many such recipients (writers, actors, etc).

So, perhaps the approach is to implement a buyLicense interface. There would be no false events this way, and no arbitrary values. With a set license price, and set royalty rate, the contract is on full auto, and so are licensing deals.

For royalties upon sale, a transferWithRoyaltyPayment method, which when invoked properly transfers the payment, emits the event, and then calls the safeTransferFrom method as usual.

Nokhal commented 3 years ago

buyLicense royalty rate

How do you propose the proper sale price is oraclized ?

NHQ commented 3 years ago

How do you propose the proper sale price is oraclized ?

As with royalty rate set by the originator, the current owner should be able to set a license price, or a minimum. A minimum would allow secondary markets to resell licenses. But, technically, does it matter what the license price is? The buyLicense method would cut the royalty percentage of any value sent. Perhaps a minimum is required by the EIP, or else an empty purchase simply does not emit events, but only increments a count (a license could be a new NFT of the same mint, with different meta)

If an owner needs spot pricing from an external oracle, it's on them to update the price. Or... enter an oracle interface EIP, which interoperates with market making contracts to get spot pricing. Here, the owner needs to be able to connect and disconnect their preferred oracle nets, via supportsInterface switchboard (or something like that ;^)

Fwiw, as I design contracts now, I am seeing the need for more controls altogether. Turn off licensing. Turn on auction. Reset button. These kind of things. And as I mentioned above, royalties may need to be paid to many distinct recipients, and possibly chains of owners, each with a royalty demand.

seibelj commented 3 years ago

Sorry to take this is a new direction...

All of that can be put into a separate contract. royaltyInfo() returns the total percent paid and to which address. That address can be a contract where you do whatever shenanigans you would like. Not for this EIP.

I think it is far better to remove the problem in the first place: no onRoyaltiesReceived call and no event.

@wighawag the more I think about your arguments, the more I think you are right. We are all in agreement on royaltyInfo() returning an address and a total percentage amount for the tokenId. This will move the ecosystem forwards.

We are in disagreement on what the mechanism for actually paying and notifying the recipient. This can be done many ways.

@jamesmorgan @VexyCats @blmalone I am in agreement that we reduce the scope of EIP2981 to just royaltyInfo(). Then I will start a new EIP that pursues the vision of payment using onRoyaltiesReceived(). But others can create their own EIPs for whatever they like.

EIP2981 will inform entire ecosystem of who gets paid, and what percentage on sale. This will let all the marketplaces begin to respect each others' royalty percentages.

Lenoftawa commented 3 years ago

@seibelj Thanks! I have been watching this thread for months. The event and how it ties into the payment needs to be discussed in a much broader scope. I wholeheartedly agree with splitting this thing. If payment cannot be enforced or socially coerced then royaltyInfo() is all that's required. However, if the discussion is now (hopefully) limited to royaltyInfo() I have a strong opinion that it needs to go beyond a simple percentage. I believe this has been proposed before - it should require the caller to provide a price and the function should return the fee. The only constraint being that the call must be gasless. NFTs will be used in many contexts where the percentage might vary with price, time, length of ownership etc.

seibelj commented 3 years ago

However, if the discussion is now (hopefully) limited to royaltyInfo() I have a strong opinion that it needs to go beyond a simple percentage. I believe this has been proposed before - it should require the caller to provide a price and the function should return the fee. The only constraint being that the call must be gasless.

I disagree very highly. If every single price can result in a different result based on some complex logic, then it's impossible to know with certainty what the royalty percentage is. It's a "Ask and you shall find out" type of thing. That is very different than the standard royalty model used in industry, and also different from existing marketplaces like OpenSea, Rarible, and Mintable where every NFT has a fixed royalty percentage. "NFTs will be used in many contexts where the percentage might vary with price, time, length of ownership etc." this is not a typical use case in the world.

I'm not saying your need isn't valid, and that royalty payments like that wouldn't be cool, but I think that would be a new EIP. The model we are proposing here maps well with standard industry practices regarding royalty payments, and are quite simple. Complexity can come when the money is received. The caller can easily enough multiple a percentage by a payment amount, this isn't difficult.

aklos commented 3 years ago

Why not create a standard that's similar to ERC721Metadata (https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#ERC721Metadata)?

People have mentioned that not all transfers are sales. Okay, so why not just provide a hook to let contract writers add royalties to the transfers that ARE sales? I assume basically all NFT contracts that would want to include royalties also have a buy method.

The ERC721Metadata interface adds a hook you can call when minting a token: _setTokenURI. You could add a similar hook for contracts that have a method like buyToken a la _sendRoyalties(tokenId, fullPricePaid). To enable that you'd need to have some mappings (ie. token => creator, and token => fee).

Currently, it seems to be a roundabout way of essentially still just hoping the platform or contract owner is making the appropriate calls while making it more convoluted and error-prone.

Sorry if these questions are redundant or off-base, I'm still a noob.

seibelj commented 3 years ago

The ERC721Metadata interface adds a hook you can call when minting a token: _setTokenURI

The onRoyaltiesReceived() model is similar - you call this when you pay. But again, all of the magic you want can go in separate EIP in regards to payments. I am now proposing we have royaltyInfo() only in this EIP.

dievardump commented 3 years ago

We are all in agreement on royaltyInfo() returning an address and a total percentage amount for the tokenId. This will move the ecosystem forwards.

I'm not in agreement with that. I think royaltyInfo should only return a percentage, and the market contract should send the corresponding value to the NFT contract, which knows what is to be done with it (transfer it to the recipient or store it for later claim). Market contracts shouldn't know who's the recipient of the royalties. This could lead, someday, to "blacklisting" of some addresses.

This is also why I think we should have onRoyaltiesReceived and an event:

Market send over the value, the NFT contract verifies it, and emits an event. Then we can know what transactions are actually Royalties payments.

Receiving tokens from a completely stranger contract without being able to track why, is "broken".

There are legal implications in receiving eth as payment for royalties. If the royalties can be sent at a different time than the sale, then if we have nothing to track, this won't work. This is why we need an event or something that logs the fact that this transfer is a payment of royalties. And we need to be able to verify that the value declared was actually sent.

wastedheadshop commented 3 years ago

These are my tokens !!!!!! I am the genuine owner if anyone can help me I will pay generous or share can be organised I have all my identification so no fear please contact me +447902416553

Get Outlook for iOShttps://aka.ms/o0ukef


From: Simon Fremaux @.> Sent: Thursday, April 22, 2021 11:07:24 PM To: ethereum/EIPs @.> Cc: Subscribed @.***> Subject: Re: [ethereum/EIPs] Discussion for ERC-721 Royalties EIP. (#2907)

We are all in agreement on royaltyInfo() returning an address and a total percentage amount for the tokenId. This will move the ecosystem forwards.

I'm not in agreement with that. I think royaltyInfo should only return a percentage, and the market contract should send this value to the NFT contract, which knows what is to be done with it. Market contracts shouldn't know who's the recipient of the royalties. This could lead, someday, to "blacklisting" of some addresses.

This is also why I think we should have onRoyaltiesReceived and an event:

Market send over the value, the NFT contract verifies it, and emits an event. Then we can know what transaction are actually Royalties.

Receiving tokens from a completely stranger contract without being able to track why, is "broken".

There are legal implications in receiving eth as payment for royalties. If the royalties can be sent at a different time than the sale, then if we have nothing to track, this won't work.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fethereum%2FEIPs%2Fissues%2F2907%23issuecomment-825214698&data=04%7C01%7C%7C78c805d2b8034b8f9df308d905db0258%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637547260454242852%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=CAjxtnXl8Ru3iNjar5qCGCKAJkOPC%2BjsDdpQi%2BlOE8o%3D&reserved=0, or unsubscribehttps://emea01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAI4GY45GR7MC2C3XIJ2LVMTTKCMZZANCNFSM4QLKBMBA&data=04%7C01%7C%7C78c805d2b8034b8f9df308d905db0258%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C637547260454247838%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=IdV%2FwlfcU%2FMNuqQusjU875BjLPRZgGCxlQBXtib7oLU%3D&reserved=0.

VexyCats commented 3 years ago

@wastedheadshop Please remove your comment and only post to the discussion if you are discussing the EIP/code we proposed.

Lenoftawa commented 3 years ago

@seibelj True, constant percentage royalties are by far the typical case, although that may be be because the marketplaces do not offer anything else. If this EIP constrains royalties to a percentage only, then I would prefer @VexyCats' original proposal (and @wighawag's earlier recommendation) that separate the concerns of 'determining the fee' and the 'identifying the recipient' as 2 distinct gassless getter functions. This would leave the option to introduce a new means of discovering the royalty amount, without need to add a new way of getting the address of the recipient.

@dievardump

...the market contract should send the corresponding value to the NFT contract, which knows what is to be done with it (transfer it to the recipient or store it for later claim). Market contracts shouldn't know who's the recipient of the royalties. This could lead, someday, to "blacklisting" of some addresses.

This is also why I think we should have onRoyaltiesReceived and an event:

Market send over the value, the NFT contract verifies it, and emits an event. Then we can know what transactions are actually Royalties payments.

I agree with the intent of the above, but I believe it can be fully achieved in a separate EIP. By placing the responsibilities that you describe on a contract that supports a new RoyaltyRecipient interface, an NFT can choose to handle the payment and emit the event simply by supporting the RoyaltyRecipient interface and returning itself as the Recipient. Alternatively, the NFT can return a reference to another contract that can handle the payment and emit the event.