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);
    }
}
Lenoftawa commented 3 years ago

@dievardump

As for token vs ETH, I assumed the royalty is paid in the token used for the purchase, so royaltyInfo does not need to consider it.

I think the type could matter, because if you pay with 10**18 in Eth and pay with10**18 in Doge, the curve could be different. But this is then some very specific use cases that we probably don't want to go in in this EIP

I think this version could do right until something broader (a lego?) comes out

I think we do need to standardise it in this EIP for the reasons that you give. If we don't return a fixed percentage, then the amount is absolutely bound to currency, so we must either:

  1. State that payments must be in ETH within this standard. OR...
  2. State that there must be side agreement between NFT and marketplace.

I don't like 2. because it effectively becomes non-standard. I prefer to keep it closed and inconvenient than just open the doors to a hundred different ways of communicating this information.

TBH, I'm very surprised there is not an EIP for negotiating payment token types between contracts (at least I cannot find one).

dievardump commented 3 years ago

For me the biggest concern is to be able to effectively follow royalty payments and to give a very easy tool (events) for royalty recipients to see for what token they have been paid, because it is extremely important for collaborations and all.

This EIP does not want to manage this, so I'm more than happy with royaltyInfo(tokenId, amountPaid) returns (recipient, amountRoyalties) because it gives more flexbility to the NFT contract, and at least helps a bit in the Royalties direction.

I actually alrady implemented the interface to royaltyInfo(uint256,uint256) in a contract I'll deploy in the next days. Because I think this is the best we can get from what we have here.

Lenoftawa commented 3 years ago

@dievardump To close this EIP using the proposed function( royaltyInfo(tokenId, amountPaid) returns (recipient, amountRoyalties)) and move on to the payment EIP, we need to create a PR with the interface you suggest, and also add the rules:

  1. The marketplace MUST call getRoyaltyInfo() with the sale price immediately before displaying the royalty (if they choose to do so)?
  2. The royalty returned MUST be paid to the royaltyReceiver in ETH, unless another token is indicated by the NFT or royaltyReceiver.

Is this what we are agreeing to?

Nokhal commented 3 years ago

I suggest adding WETH to the list of accepted currencies.

seibelj commented 3 years ago
  1. The royalty returned MUST be paid to the royaltyReceiver in ETH, unless another token is indicated by the NFT or royaltyReceiver.

Is this what we are agreeing to?

Nothing about that function call says anything about what to token can be paid. It works the same for ETH, WETH, USDC, etc. A future EIP can be used to further specify payment details.

The benefit of this EIP will be every marketplace being able to read NFTs created by other systems and determine the royalty payment, although the payment itself will be bespoke to their system until a future EIP is made.

The one thing I would consider we add is one more field to the response and make it a triplet:

royaltyInfo(uint256 tokenId, uint256 amountPaid) returns (address recipient, uint256 amountRoyalties, bool isPercentFixed)

bool isPercentFixed returns true if the royalties are always fixed for that tokenId - meaning you can easily know what the percentage is, and that it will never change regardless of price, for that tokenId. If it's false, then you must always introspect based on the sale price. This will cover both the common fixed-percent situation and the uncommon (but future-proof) situation where the percent changes based on price, the day of the week, the full moon, whatever crazy stuff we can't even predict. It also signals to buyers that the royalty rate could change completely in the future. It helps the market determine price.

If isPercentFixed returns true:

  1. royaltyInfo MUST return the same percentage of amountRoyalties for every amountPaid.
  2. royaltyInfo MUST return the same amountRoyalties for a given amountPaid in perpetuity.

This allows caching in marketplaces and other systems with confidence it will remain accurate. And if this is too restrictive, return false for isPercentFixed and go crazy with innovation.

dievardump commented 3 years ago

I think that the isPercentageFixed is too much.

We shouldn't care if it's ETH or WETH or DOGE, and just return the amount we want from the amount sent.

Because else we're starting to complicate this too much and then if we want to complicate, we better go all the way in the complication and make this EIP "full royalty needs".

isPercentageFixed would be complicating the work on both side (NFT contract and Marketplace), for something used maybe in less than 1% of the case.

Also it cost less effort, storage and code for a marketplace to always introspect, than to manage a cache.

seibelj commented 3 years ago

If we have some totally EIP-friendly way to change the royalty amount between each block, I think this is a big mistake, which is why I want to specifically call it out. Fixed royalty percentage is the 99% case and industry standard. The varying percentage based on amount purchased is the 1% case but a bunch of people here were advocating for it, so in the interest of moving forwards, rather than ban it entirely we could support it with a boolean.

Because else we're starting to complicate this too much and then if we want to complicate, we better go all the way in the complication and make this EIP "full royalty needs".

Given we can't even easily agree to a simple function to return a royalty percentage without bike shedding for months on end we definitely do not want to increase the scope of this EIP any further lest we delay it another 12 months.

seibelj commented 3 years ago

I think we have broad consensus on:

royaltyInfo(uint256 tokenId, uint256 amountPaid) returns (address recipient, uint256 amountRoyalties)

If some NFT creator wants to make amountRoyalties change wildly with price, that's on them. I believe this will create confusion and would prefer:

  1. royaltyInfo MUST return the same percentage of amountRoyalties for every amountPaid.
  2. royaltyInfo MUST return the same amountRoyalties for a given amountPaid in perpetuity.

But if people don't want this restriction, and also don't want any way to distinguish between the 99% case (fixed percent) and the changing-all-the-time contracts, so be it.

But I think we need to move forwards, we have something like 70% consensus on royaltyInfo as described above.

dievardump commented 3 years ago
  • royaltyInfo MUST return the same percentage of amountRoyalties for every amountPaid.

  • royaltyInfo MUST return the same amountRoyalties for a given amountPaid in perpetuity.

I disagree with both. Why would we do that?

Introspection + royaltyInfo call at every sale is enough. Why would we block NFT contracts to do fancy things, it doesn't change anything on the market contract.

The market queries how much is needed, and the NFT contract returns it. No more question about it. If the marketplace wants to create some cache, that's their problem, but there is no reason to force NFT contracts to follow rules like this. None.

royaltyInfo(uint256 tokenId, uint256 amountPaid) returns (address recipient, uint256 amountRoyalties)

royaltyInfo(10, 5000000) => (0xdead, 500000) on the first call royaltyInfo(10, 5000000) => (0xdead, 50000) on the second call should be perfectly valid

The varying percentage based on amount purchased is the 1% case

This is not only about amount purchased. this could be because the creator has only set royalties for a given time, or royalties decrease over time, or every time there is a transfer, or because royalties are less if people use the creator Sale contract, or for hundreds of reasons we don't know because we can not know all use case and because we are too set with "the old ways" (i.e. how royalties are handled in the non crypto space right now.)

We are seeing new ways of creating content, and we shouldn't block the capacity of people to creating new ways for royalties and creators to be rewarded because we are stuck in our mind with the old ways of doing it.

I think blocking flexible royalties would be a big mistake.

nullren commented 3 years ago

are royalties always expected to be some scaled multiple of the amount paid, ie amountRoyalties = x * amountPaid where x changes depending on how/when royaltyInfo is called?

if we had more information about the currency the transaction was paid in, we can have more sophisticated (and common) royalty payment schemes.

for a specific use case, let's say i want royalties to be the minimum of 5% amount paid or 5 USDC. I'd need to know the current of the amount paid to be able to do my own conversion of that currency to USDC to adjust amountRoyalties. this has been something i have been asked by someone and told them it was not possible at the moment. but with a little more information about the currency, i could make a best effort to do this.

seibelj commented 3 years ago

for a specific use case, let's say i want royalties to be the minimum of 5% amount paid or 5 USDC. I'd need to know the current of the amount paid to be able to do my own conversion of that currency to USDC to adjust amountRoyalties. this has been something i have been asked by someone and told them it was not possible at the moment. but with a little more information about the currency, i could make a best effort to do this.

This can be included in an EIP that builds on this one - no reason it cannot.

dievardump commented 3 years ago

for a specific use case, let's say i want royalties to be the minimum of 5% amount paid or 5 USDC.

Yes that's why this question came actually

But adding this would bring other problems:

What if the element is sold for less than 5 USDC? The interface would need to know that and ask the buyer to add 5 USDC more to the asked selling price. That doesn't really seem fair for the buyer neither. And a lot more complicated to do in the interface.

With the idea that the royalties is derived from the selling price, we ensure that there is at least enough in the current transaction to sustain it.

wighawag commented 3 years ago

This can be included in an EIP that builds on this one - no reason it cannot.

Maybe we could pass in a extra bytes data parameter that future EIP could define further

dievardump commented 3 years ago

Maybe we could pass in a extra bytes data parameter that future EIP could define further

To the royaltyInfo call, the returned data, or both?

wighawag commented 3 years ago
function royaltyInfo(uint256 tokenId, uint256 value, bytes calldata data) external view returns (address receiver, uint256 royaltyAmount);
wighawag commented 3 years ago

but maybe a more generic one would be

function royaltyInfo(uint256 tokenId, uint256 value, bytes calldata data) external view returns (address receiver, uint256 royaltyAmount, bytes memory roylatyPaymentData);
dievardump commented 3 years ago

but maybe a more generic one would be

function royaltyInfo(uint256 tokenId, uint256 value, bytes calldata data) external view returns (address receiver, uint256 royaltyAmount, bytes memory roylatyPaymentData);

For the sake of allowing other EIP to build on this one, I think this is a good option. Both side being able to send arbitrary bytes.

seibelj commented 3 years ago

I also approve of @wighawag proposal. I like that it's generic and allows further improvement. I'll give it a few days for discussion, then I will update the EIP document.

Lenoftawa commented 3 years ago

@wighawag @seibelj I can go with extra data option. We are punting the problem down the road, and that's fine, but what goes in that field must be subject to standardisation or it becomes useless. For that reason we must call out in this EIP that it is strictly RFU. We will define what it is in a future EIP.

Currency issue must be resolved because it is related to percentage issue. We can't let an NFT advertise a non-percentage amount without the ability stating what the units are. There are 2 solutions:

I can go with either (although I prefer stating ETH and allowing non-percentages).

Everything else is in a new EIP.

seibelj commented 3 years ago

Currency issue must be resolved because it is related to percentage issue. We can't let an NFT advertise a non-percentage amount without the ability stating what the units are.

This is a very edge case. How many NFTs would charge, say, 5% for USDC, 2% for ETH, 10% for WBTC? These exotic use-cases should be handled in an EIP that builds on this one, which the arbitrary data fields enable.

However, if people feel this is absolutely crucial, we could add:

royaltyInfo(
    uint256 tokenId,
    address tokenPaid,
    uint256 value,
    bytes calldata data
)
external view returns (
    address receiver,
    uint256 royaltyAmount,
    bytes memory royaltyPaymentData
);

address tokenPaid would be the contract address for the ERC20-compliant token paid, or set to null for native asset (ETH).

dievardump commented 3 years ago

address tokenPaid would be the contract address for the ERC20-compliant token paid, or set to null for native asset (ETH).

If we do that, then we should already include ERC1155. That's exactly what I meant here https://github.com/ethereum/EIPs/issues/2907#issuecomment-829345811: it could matter, but if we start going there, then we need to do both ERC20 and ERC1155.

However, that's something that ̀data` can handle with further EIP.

This one should be the base we build on, let's keep it simple and not set any restriction on what it allows, nor in what currency we're talking. this should be handled by extensions to this standard. This one as @wighawag proposed would handle 99% of the case. It's good enough for something that can easily be built on with the bytes.

Lenoftawa commented 3 years ago

@seibelj

This is a very edge case. How many NFTs would charge, say, 5% for USDC, 2% for ETH, 10% for WBTC?

That was not the problem I was concerned about. I'm not suggesting we encourage creation of NFTs that have different curves for different currencies. But that is another potential consequence of the interface we have created.

A more plausible example is that an NFT wants to define a minimum (5 USD +1%) or a fixed fee (1 USD). We have created an interface that allows us to do this.

To be a standard, we need to explain how this works in all cases or state the constraints within which it can be used.

We either make it possible for the marketplace to unambiguously interpret the returned amount, or we admit it is a percentage and revert to that interface. The non-percentage option can be resolved without dealing with multiple currencies, but it requires us to pick a common a unit and outlaw caching.

The IsPercentage flag would solve a good part of the problem.

@dievardump

It's good enough for something that can easily be built on with the bytes.

Two thoughts on this.

  1. If we plan to use extra data in a future EIP then we need to make sure that it is not used randomly by contracts. That's why we must state that it is not to be used as part of this EIP.
  2. The problem with 'extra data' used in this way is that it is part of IEIP_2981 interface. When we create functions in the next EIP, we will need to define a new interface, but then need impose extra semantics on functions in IEIP_2981. Any NFT built to IEIP_2981 will not be aware of the new semantics of the extra data. This means that the marketplace must check all NFTs to determine whether it supports the semantics of the extra data; it can't just check to see if it supports the IEIP_2981 interface. It is messy. In my view, it is better to solve the issues relating to this EIP within this EIP, and any EIP build on top should not change the semantics of functions in this EIP.
wighawag commented 3 years ago

Just a quick comment on

The IsPercentage flag would solve a good part of the problem.

This is not really true. percentage could have reason to be changing too. Actually a contract that allow multiple creator, could let the creator change it at will

I really think we should embrace the fact that the royalty amount is a changeable value and that if marketplace want to cache, they take responsibility for it.

seibelj commented 3 years ago

I really think we should embrace the fact that the royalty amount is a changeable value and that if marketplace want to cache, they take responsibility for it.

The problem with not clearly stating it's a fixed rate, is that the marketplace can't even say 5% Royalty Fee as part of the NFT data, as they simply don't know what the percentage is for any given payment amount. It could be 5%, then 25%, then 2%, etc. depending on what the purchase price is. All the marketplaces currently support a fixed fee model, as that is what a user would expect and is the 99.999% use case. This is the model throughout the normal licensing industry as well.

But all that said, I don't really care, as all the contracts our company makes will be fixed fee, and any NFT contract that uses some exotic model probably will be ignored / banned by major marketplaces unless they have a very strong reason to exist as it will confuse their users and be bad user experience.

@lenifoti

If we plan to use extra data in a future EIP then we need to make sure that it is not used randomly by contracts. That's why we must state that it is not to be used as part of this EIP.

Agree

The problem with 'extra data' used in this way is that it is part of IEIP_2981 interface. When we create functions in the next EIP, we will need to define a new interface, but then need impose extra semantics on functions in IEIP_2981. Any NFT built to IEIP_2981 will not be aware of the new semantics of the extra data. This means that the marketplace must check all NFTs to determine whether it supports the semantics of the extra data; it can't just check to see if it supports the IEIP_2981 interface. It is messy. In my view, it is better to solve the issues relating to this EIP within this EIP, and any EIP build on top should not change the semantics of functions in this EIP.

Not sure I follow. A marketplace will announce support for 2981, then it will check that via 165. If a marketplace also announces support for EIP#### that builds on 2981, then they:

  1. Completely ignore 2981 because they only support ####
  2. Check for both 2981 and ####, and if supporting ####, that overrides it.

Marketplace can support any manner of superior EIPs.

Lenoftawa commented 3 years ago

@wighawag

This is not really true. percentage could have reason to be changing too. Actually a contract that allow multiple creator, could let the creator change it at will I really think we should embrace the fact that the royalty amount is a changeable value and that if marketplace want to cache, they take responsibility for it.

True, even returning a percentage does not preclude the NFT returning a percentage that varies. That would have to be stated.

So the 2 cases are:

  1. A fixed percentage, fixed for perpetuity. In this case the NFT doesn't need to know the sale price.
  2. All bets are off, market place must pass in the sale price and cannot cache the amount returned.

Number 2 brings in an:

We agree that option number 2 would be useful but its presence is bogging us down from the majority case (Number 1).

Can I suggest that we create 2 separate interfaces. E.g ISimpleRoyalty (fixed%) and IFlexiRoyalty (all bets off), where the second requires and extends the first.

And of course, I would prefer that the ISimpleRoyalty does not change the semantics of functions in IFlexiRoyalty. Although I concede that it can be made to work that way.

PS. Better names welcome.

dievardump commented 3 years ago
2\. **All bets are off**, market place must pass in the sale price and cannot cache the amount returned.

Number 2 brings in an:

* an extra parameter,

* the need to define rules about caching and

* the need to consider currency.

I don't understand this. There is no need for more parameters than what is here (https://github.com/ethereum/EIPs/issues/2907#issuecomment-831352868) for this to work.

There is absolutely no reason to think that any marketplace will cache the result of this call for a specific ID. This is thinking that marketplaces have the luxury to:

Introspection will be 99.99% of the case. No marketplaces will cache, they will introspect and query for every sale. The cost is low and this will be more accurate to what the creators want, because creators can actually change royalties at any moment (I don't say they should, but they can, so caching is kind of out of question).

I understand the idea of defining how to use the bytes, but the fixed or not fixed royalties %age should not be part of this EIP.

Sending the amount the NFT is sold for, and getting back an amount that should be paid as royalties is enough

wighawag commented 3 years ago

the majority case (Number 1).

I am not really sure this is the case. we already have lots of contract (opensea, rarible, zora) that are cross artist and in that case, it would be natural to let the creator adjust the royalty or set it on and off.

I am not sure I follow the concern of marketplace in regard to caching. I actually do not see why they even need to do caching.

On the frontend they would need to make call to the token contract anyway to protect users from performing a transaction doomed to fails. and so asking them to do an extra call on royaltyInfo should not be much.

With that considered. what is the issue of simply describing in the EIP that marketplace need to call royaltyInfo at the point of purchase and that is it. No extra flag or introspection required.

Also note that if we have these introspection flag, it would not help marketplace for the contracts that have that flag disabled and require royaltyInfo to be called.

it would be simpler to simply require marketplace to do the call in all cases

NBaron commented 3 years ago

I am in favor of this signature:

royaltyInfo(
    uint256 tokenId,
    uint256 value,
    bytes calldata data
)
external view returns (
    address receiver,
    uint256 royaltyAmount,
    bytes memory royaltyPaymentData
);

Which implies that:

  1. No constraints on how royaltyAmount computation is implemented, it can be percentage based or something else, anyway, it stay as an implementation detail.
  2. The potential benefit of caching by marketplaces does not seem very evident and could be more complex to implement on the marketplace side.
  3. If there is a need to advertise/communicate the royalty pattern (percentage, fixed, etc.), would not it be possible to state it in the token metadata using the URI? (yeah, off-chain).
  4. Futur proof thanks to the bytes argument and returns.

I am not sure to understand the need to specify the currency with an extra argument. Because the payment transaction is not part of this EIP, I suppose it is not needed for the contract to know the currency. I have read all the messages here but still, can someone explain a use-case for this?

Thanks

seibelj commented 3 years ago

It's not about caching. It's about display. If you go to an NFT detail page, it would say "Royalty: 5%". But with variable royalty based on payment amount, you can't say this. You need to wait until someone tries to buy to show this key detail to the user.

Which goes back to, "it would be nice to know if it's fixed or not". I like passing in the purchase amount so there can be no confusion about how much is owed, no calculation issues, etc. But letting the marketplace know a fundamental thing - whether the percent is fixed - would be unknown without some additional piece of data.

But again - I don't really care, I believe NFTs that start doing variable royalty percentages, changing based on the amount paid, time of day, full moon, etc. without specifying another EIP that explains how it works will be blacklisted / unsupported from marketplaces because it won't be easy or sensible to support, and possibly a weird attack vector or exploit by confusing users (5% until it goes above $1000 then suddenly it's 99%!). But that's my opinion.

@NBaron I am in agreement. But some people here are very forceful about also including which token the buyer is paying with, which would necessitate one additional parameter as I explained here https://github.com/ethereum/EIPs/issues/2907#issuecomment-831650224

Lenoftawa commented 3 years ago

Sorry, I was using 'caching' in its broadest sense. That is that the marketplace makes an assumption that it can trust that the number won't change after a call is made. And the most obvious case is that it displays it. So yes. It is about displaying it.

wighawag commented 3 years ago

Re display

We have to remember that royalty payment would be a voluntary act from marketplace. they do not need to be accurate at the second and so to ensure they perform the royalty displayed, they could fetch it at the point of display of the NFT and use that rate when the purchase is made. (it become part of the purchase tx data to ensure this is what is used then)

In any case, we want to support fluctuating royalty and saying we could allow contract to publicize that they do not allow change, would not help the contracts that allow creator to change or disable/enable royalties.

Lenoftawa commented 3 years ago

Regarding whether fixed percentage is 99% I don't think it matters. If a fixed percentage is a useful option for a significant number, why not allow it as a separate EIP so we can get on to a more comprehensive solution without dragging it out for those that don't need it. I also believe separation will help move the comprehensive option along faster by removing the objections that we hearing, and complexity (potentially)

seibelj commented 3 years ago

We have @NBaron proposal https://github.com/ethereum/EIPs/issues/2907#issuecomment-832718742

And mine which is identical except one additional param to specify the asset we are paying with https://github.com/ethereum/EIPs/issues/2907#issuecomment-831650224

Let's take a poll - which option do you prefer:

  1. @NBaron option which excludes which asset the user pays with https://github.com/ethereum/EIPs/issues/2907#issuecomment-832718742
  2. @seibelj option which includes which asset the user pays with https://github.com/ethereum/EIPs/issues/2907#issuecomment-831650224
  3. Neither
NBaron commented 3 years ago

Sorry, I was using 'caching' in its broadest sense. That is that the marketplace makes an assumption that it can trust that the number won't change after a call is made. And the most obvious case is that it displays it. So yes. It is about displaying it.

Would it be possible to use the token metadata for this, via a specific property in the JSON file?

When the marketplace displays the page of the token, it is already fetching its metadata using the token's URI to get the JSON object. Then it could look at:

royaltyData: {
    strategy: String, // FixedValue, FixedPercentage, Custom
    value: Number, // Percentage or fixed value or ...
    description: String,
    ...
}

If yes, this EIP could skip the concern of "caching/displaying by marketplace" and describe the metadata addition.

NBaron commented 3 years ago

We have @NBaron proposal #2907 (comment)

And mine which is identical except one additional param to specify the asset we are paying with #2907 (comment)

Let's take a poll - which option do you prefer:

  1. @NBaron option which excludes which asset the user pays with #2907 (comment)
  2. @seibelj option which includes which asset the user pays with #2907 (comment)
  3. Neither

Regarding the option 2, perhaps I am missing something. What could be the use-cases? Even exotic ones. Something like, 5% if paid with ETH, but exemption of royalties if paid with ERC20Xyz because it is very specially linked to the NFT's creator/owner/market?

It is really an open question, I would be glad to vote for 2 if the benefits of these use-cases are proven.

dievardump commented 3 years ago

I am for number 1.

Also if we go with option 2, we need to add support for ERC1155 too (so another uint to add the amount/tokenId)

It is an underrated way of creating a lot of FT in one contract, that can and will be, in the future, used to create erc20 equivalent. If we start doing "per token" differentiation, there is no reason to not add it.

Lenoftawa commented 3 years ago

I lean towards number 1 for the reason that I don't think we should bring currency negotiation into this EIP. However, returning a number is meaningless without either a) an implied unit or b) a guarantee that it is a fixed percentage in perpetuity.

To leave it up to the NFT and marketplace to somehow pre-agree on what the returned number means defeats the idea of a standard - it's a non-standard.

@seibelj Saying 'I don't really care, as all the contracts our company makes will be fixed fee' requires that the marketplace knows that your company created the NFT, or that you have some private non-standard arrangement. This defeats the standard.

Having said that, I understand your point, and I agree that the standard must support the needs of companies that pick this strategy. I don't see how we can leave these things silent in a standard

By the way, when you say 'fixed fee' above, do you mean the royalty amount never changes or the royalty percentage never changes?

Lastly, the proposed signatures have an additional argument for passing 'extra data' with the expectation that it must be reserved for future use. As such, it is unusable under this EIP and yet it leaves a tantalising invitation to do so. So I question it's value. A new EIP can define a getter function to retrieve the same information (whatever it is) in a properly structured way, instead of casting it from a bunch of bytes.

seibelj commented 3 years ago

By the way, when you say 'fixed fee' above, do you mean the royalty amount never changes or the royalty percentage never changes?

We believe that NFT purchasers should know what they are buying to properly assess them, and that includes a fixed-fee for any price resale value and a locked in royalty in perpetuity. It would be confusing and market-warping to say "5% under $1000, 20% above $1000" and also unfair to pull the rug on them 5 years after sale, and change the royalty from 5% to 25% just because we can.

Having a very high royalty percent is fine provided you are upfront about it. Then the free market can properly assess an NFTs longterm value. Having very confusing royalty model, or something that can change unpredictably, is IMO not fair nor easily digestable to users.

But that said, I support innovation and new models, and any NFT creator can do whatever they want. I'm just trying to emphasize that the 99% way is what I am describing, at least until some new model catches on (if ever), which I don't think will happen because of the fairness and confusion issues I described above.

seibelj commented 3 years ago

I am going to write up Option 1 tonight and update the EIP. I think the unused bytes is a good balance of future proofing and encouraging innovation without making it too verbose.

jamesmorgan commented 3 years ago

Just caught up, I also vote number 1, let's get this baby moving!💪

wighawag commented 3 years ago

Agreed, lets' go with 1

blmalone commented 3 years ago

Apologies for the delay here. Been catching up on this thread.

I also agree with Option 1.

I think that this finds the balance between keeping this EIP lightweight and future proof.

/**
  * @dev Returns an NFTs royalty payment information
  *
  * @param _tokenId  The identifier for an NFT
  * @param _value Purchase price of NFT
  * @param _data Additional data for royalty info. Not to be used as part of EIP-2981.
  *
  * @return _receiver The royalty recipient address
  * @return _royaltyAmount Amount to be paid to the royalty recipient
  * @return _royaltyPaymentData Additional data for royalty info. Not to be used as part of EIP-2981.
  */
function royaltyInfo(
    uint256 _tokenId,
    uint256 _value,
    bytes calldata _data
)
external view returns (
    address _receiver,
    uint256 _royaltyAmount,
    bytes memory _royaltyPaymentData
);

Am I understanding the additional data fields correctly here?

Lenoftawa commented 3 years ago

@blmalone I believe that's the signature we have converged on. We still need to nail some of the semantics.

I see a couple of open questions that are best viewed from the perspective of a Marketplace. In essence: what is the expected behaviour of a Marketplace that is written to consume IERC_2981?

Firstly, how will an ERC-2981 compliant Marketplace detect that _royaltyPaymentData is 'unused'? Others may have an approach in mind. Solidity is evolving, so I'd prefer to make it an explicit value rather than rely on any complier behaviour lest we are stuck with a maximum complier version. Also, by making it a known value, we can avoid that value when we extend the interface.

Secondly, how must an ERC-2981 compliant Marketplace interpret _royaltyAmount when it detects that the NFT has left royaltyPamentData 'unused'. In other words, what does the NFT mean when it returns royaltyPamentData = unused?

There are 3 options that come to mind:

  1. The Marketplace can trust that the amount is a percentage of _value and that the percentage is fixed for ever.
  2. The Marketplace knows that it cannot make assumptions about the relationship between _value and _royaltyAmount, nor can it assume that a given_value won't return a different _royaltyAmount at a different time. But _valueand_royaltyAmount` must be in a default unit of account (eg USD, BTC, ETH) and therefore calculations and currency conversions can be performed.
  3. The Marketplace cannot make any assumptions about the meaning of amount, its just a number and the meaning has to be negotiated outside this EIP.

For me, option 3 means that this EIP cannot stand on it's own and therefore is of no value. Option 2 is a little complex and an odd compromise, I prefer to make this part of the future extensions if we need to.

That leaves 1. I like option 1 because it is simple and (from what I hear) solves most problems that Marketplaces and NFTs have today. Form a pragmatic point of view, it removes scope for disagreement and moves us along.

It goes without saying that a Marketplace that is compliant with EIP-2981 cannot charge royalties on NFTs that return a value for _royaltyPaymentData that is not 'unused', unless the Marketplace is also compliant with an extension EIP that defines the behaviour of _royaltyPaymentData, and the NFT supports the extension EIP.

wighawag commented 3 years ago

@lenifoti

I am strongly against 1. this is too limiting and it is not compatible with the method we use: a function, that can return different thing, whether you want it or not, that's the interesting properties it has. Also we now pass the purchase amount as parameter because we want NFT contract developer to be creative with it if they want to.

If we wanted it to be fixed, as I mentioned in an earlier response, the best would be to use events, but this has its drawback and might not practical if we want that to be emitted for all tokenId.

I always thought we were going with option 2.

Lenoftawa commented 3 years ago

@wighawag If by 'option 2' you mean:

The Marketplace knows that it cannot make assumptions about the relationship between _valueand _royaltyAmount, nor can it assume that a given _valuewon't return a different _royaltyAmountat a different time. But _valueand `_royaltyAmount`` must be in a default unit of account (eg USD, BTC, ETH) and therefore calculations and currency conversions can be performed.

Then that works for me.

But do you agree there must be a unit of account that is agreed in a way that is covered by this standard?

wighawag commented 3 years ago

But do you agree there must be a unit of account that is agreed in a way that is covered by this standard?

why ?

I thought we agreed that adding the token should not be part of that standard but that the extra data parameters could be used for that in later standard

Lenoftawa commented 3 years ago

...but that the extra data parameters could be used for that in later standard

This is the part I can't compute, we have to make this EIP work. Maybe I'm missing something but I can't see how we can do that without:

The extension is not part of this EIP and private handshakes defeat the standard.

wighawag commented 3 years ago

You did not answer the why question.

why do you need the token as part of royaltyInfo ? (especially in the context of your reply in regard to fixed vs non-fixed)

I believe we do not need it. Sure it can be useful, but this can be tackled later in another EIP.

Lenoftawa commented 3 years ago

why do you need the token as part of royaltyInfo ? (especially in the context of your reply in regard to fixed vs non-fixed)

I'm not suggesting that the token should be in (I voted to leave it out). But that moves the problem into the spec, which is fine.

And I was offering 2 options for dealing with the absence of the token....

PS. I'd love to be convinced that there is no problem...

wighawag commented 3 years ago

Oh, I see your option 2 was confusing to me. I read it as follow

The Marketplace knows that it cannot make assumptions about the relationship between _value and _royaltyAmount, nor can it assume that a given _value won't return a different _royaltyAmount at a different time. the _value and _royaltyAmount share the same unit of account. _value represent the amount paid for the purchase, _royaltyAmount represent what will be given to royaltyOwner.

So no need of specifying a token there.