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);
    }
}
VexyCats 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.

I don't see this working at all. If I buy a NFT from a contract you control and resell it on a marketplace - why would the money be sent to the NFT contract? It should be sent to me - with the royalty percent being sent to whoever should receive it.

Preventing functionality like blacklisting is not in the scope of this EIP - in fact, limiting functional altogether is the anti-thesis of what an EIP should be doing. If someone wants to add blacklisting into their contract - why are we to say this EIP should prohibit that?

There are more issues with what else you suggested as well - such as hold the funds inside the contract - yet later you discuss that we need to know the funds were sent at a specific time and track this transfer, if the funds are held in the contract the dispersal of royalty payments would happen at a later time and there would be no information related to what the sale was.

All of this is going into the implementation of the NFT contract - which again is not covered under the scope of this EIP.

By simply having the contract address as the receiving address for royalties, they can then implement their own logic.... As we have suggested many times above.

dievardump commented 3 years ago

I don't see this working at all. If I buy a NFT from a contract you control and resell it on a marketplace - why would the money be sent to the NFT contract? It should be sent to me - with the royalty percent being sent to whoever should receive it.

Only the value of the royalties is sent to the NFT contract, the value of the sale (minus royalties) is sent to the seller.

The NFT contract is the one that knows how the royalty recipient wants to get their royalties (either with a direct transfer, a claiming button, a split between recipients, ...). It shouldn't be the role of the market contract to manage how the royalties are distributed: Because there are a lot of forms of royalties, a lot of ways to distribute those. Just send the royalties over, I'm dealing with it.

That's why for me the market should just query "how much royalty should I send over for this nft?", and send the corresponding value over, by calling onRoyaltyReceived.

The NFT contract can verify the amount received, and process it as the royalty recipient expect it. Because The royalty recipient chose this contract to publish their NFT, and the reason might be just because of how they handle royalties.

Preventing functionality like blacklisting is not in the scope of this EIP - in fact, limiting functional altogether is the anti-thesis of what an EIP should be doing. If someone wants to add blacklisting into their contract - why are we to say this EIP should prohibit that?

Only allowing one recipient (actually just having the recipient) in the return of royaltyInfo is a lot more limiting than having the royalty value sent to the contract with a callback like onRoyaltiesReceived, and then let the contract which is the one stating "hey we want royalties", managing the royalties as they see fit*.

There are more issues with what else you suggested as well - such as hold the funds inside the contract - yet later you discuss that we need to know the funds were sent at a specific time and track this transfer, if the funds are held in the contract the dispersal of royalty payments would happen at a later time and there would be no information related to what the sale was.

No. onRoyaltiesReceived would emit an event that royalties have been paid at the moment it is called. Because it would verify the value.

It's as simple as

nftAddress can then manage the royalties as wished by the recipient, and emit an event. If it's directly transferred or stored to be claimed later only by the recipient doesn't change the fact that the royalties have been paid and verified at this precise moment and can be found easily and verified using logs.

All of this is going into the implementation of the NFT contract - which again is not covered under the scope of this EIP.

You called this ERC-721 Royalties EIP, it's fully in the scope of this EIP to add Royalties to an ERC721 contract.

By simply having the contract address as the receiving address for royalties, they can then implement their own logic.... As we have suggested many times above.

If you do a transfer, the contract can not handle this, because it doesn't know which ID it correspond to. if you send the value OUTSIDE of onRoyaltiesReceived it can not be verified by the contract that the value has actually been sent.

I'm sorry but this doesn't work

VexyCats commented 3 years ago

I don't see this working at all. If I buy a NFT from a contract you control and resell it on a marketplace - why would the money be sent to the NFT contract? It should be sent to me - with the royalty percent being sent to whoever should receive it.

Only the value of the royalties is sent to the NFT contract, the value of the sale (minus royalties) is sent to the seller.

The NFT contract is the one that knows how the royalty recipient wants to get their royalties (either with a direct transfer, a claiming button, a split between recipients, ...). It shouldn't be the role of the market contract to manage how the royalties are distributed: Because there are a lot of forms of royalties, a lot of ways to distribute those. Just send the royalties over, I'm dealing with it.

You can do this exactly with the EIP as it stands now. Royalty Recipient is the NFT contract address, then let the contract handle its own royalty payouts.

That's why for me the market should just query "how much royalty should I send over for this nft?", and send the corresponding value over, by calling onRoyaltyReceived.

The NFT contract can verify the amount received, and process it as the royalty recipient expect it. Because The royalty recipient chose this contract to publish their NFT, and the reason might be just because of how they handle royalties.

What the receiver does or implements - is not in the scope of an EIP. If the smart contract wants to process payments and do some logic, that does not fall into the EIP's realm.

Preventing functionality like blacklisting is not in the scope of this EIP - in fact, limiting functional altogether is the anti-thesis of what an EIP should be doing. If someone wants to add blacklisting into their contract - why are we to say this EIP should prohibit that?

Only allowing one recipient (actually just having the recipient) in the return of royaltyInfo is a lot more limiting than having the royalty value sent to the contract with a callback like onRoyaltiesReceived, and then let the contract which is the one stating "hey we want royalties", managing the royalties as they see fit*.

All this can be done currently with the EIP as it stands. On calling royaltyInfo() the contract can implement any logic it likes and can return any value it likes in the form defined in the EIP.

Setting the recipient as the smart contract itself, will let the contract handle paying out to multiple people or whatever logic it likes.

dievardump commented 3 years ago

But nothing is verifiable because the value is not sent with something that says "HEY I'M PAYING FOR THIS tokenId HERE".

So no, it does not work. The smart contract (and the royalty recipient, for hundreds of reasons - taxes, collaborations, whatever) MUST be able to link tokenId and royaltyValue that is sent (and to verify it) at the time it is sent.

It's not possible now, as it stands. A simple transfer can not work.

VexyCats commented 3 years ago

function onRoyaltiesReceived(address _royaltyRecipient, address _buyer, uint256 _tokenId, address _tokenPaid, uint256 _amount, bytes32 _metadata) external returns (bytes4);

Doesn't this do exactly what your saying?

dievardump commented 3 years ago

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

Doesn't this do exactly what your saying?

1) function royaltiesRecieved(address _creator, address _buyer, uint256 _amount) external is not in the current EIP as it stands

2) No because (a) it's not payable (b) the value is not sent with the call to this function (also this function can then be called at any time, by everyone, with fake values if no verification). So the contract can not verify that this exact value was actually sent over and can not emit an event for it.

The only way for this to work, would be if the royalty value is sent with the call, and then processed by the nft contract.

I actually don't see why it's even a discussion if we should enforce the "royaltyValue" verification at the moment it's sent over.

On one side, there is a flow that allows for

On the other, with a very small modification:

VexyCats commented 3 years ago

So what about the situation where a contract has no logic for royalties or doesn't need anything special.... if it was sold for an ERC20 payment - it needs to have the functionality to accept/transfer ERC20s.

Ultimately - this ends up making other very common use cases - such as a simple royalty that is being sold on every NFT right now - much harder to implement, have higher gas costs, and ultimately cost more money for the person receiving the royalties if they have to submit a tx to withdraw the royalties.

I think your main point here is that legally or in terms of taxes your suggestion needs to be added to the EIP. But EIPs are not made to deal with tax situations or legalities. In fact its impossible - because who knows the tax laws for every country and how can we possibly implement something thinking for taxes - when Canada may have different tax requirements for royalties than france, and france has different ones than china.

This does not fall into the scope. Taxes and legalities can be dealt with via looking at the events and transactions on the blockchain and not covered within the scope of the EIP.

If you are concerned with recieving false calls to the onRoyaltiesReceived function - then the creator would simply add a check require(msg.value === _amount).

I will say - we can make the function payable - so that if for whatever reason the marketplace wants to add an additional check that

if(receiver === nftAddress){
 send payment within the call to onRoyaltiesReceived()
}

So - actually I think a good middle ground here is simply that - add payable to the function and thats it. What do you think about this?

The contract could be set as the address to receive the payments and then the value can be sent over to the smart contract via the onRoyaltiesReceived function if the marketplace.

Edit: The function I'm referring to is:

function onRoyaltiesReceived(address _royaltyRecipient, address _buyer, uint256 _tokenId, address _tokenPaid, uint256 _amount, bytes32 _metadata) external returns (bytes4);

dievardump commented 3 years ago

I think your main point here is that legally or in terms of taxes your suggestion needs to be added to the EIP.

No. Every tokenId has an history, including collaboration, amounts going to associations, etc etc... taxes is just one of the problem. We need, in this EIP, to enforce a way to link tokenId and royaltyAmount, and to verify that this amount has actually been sent. Else , it's broken.

If you are concerned with recieving false calls to the onRoyaltiesReceived function - then the creator would simply add a check require(msg.value === _amount).

Are you a Solidity developer?
This doesn't work as it stands. We would have to make the function payable. But then anyone doing this check would break any sale with royalties that are not send with the call to this function. This is why it MUST be enforced.

And asking marketplaces to add things like if(receiver === nftAddress){ is not a viable solution, because the more you complicate the implementation they have to make, the less they will support it. that's why asking them to send over, and that's it, is a loooooot easier.

However, NFT contracts need the royalties for their users, and it would take a few minutes to write the code of onRoyaltiesReceived to handle payments in eth, erc20 and erc1155 with verification of the value.

That's why, the implementation of how to manage the royalties received should be on the nft contract, not the marketplaces who's just the good guy sending money over.

This does not fall into the scope. Taxes and legalities can be dealt with via looking at the events and transactions on the blockchain and not covered within the scope of the EIP.

People shouldn't have to check every transactions that have been made to them, to try to find why they had random money sent over.

I thought we are making this standard for the artists. To make their life easier, their rights followed and make something that marketplaces will find easy to implement and therefore support. We are creating a standard to make this easy. Not to complicate artists & marketplaces life. A standard that is broken is no standard.

If someone sell one of my piece on a marketplace with a contract that is not verified, and the I can't actually see what happened during the transaction, how am I to know this was royalties? and how do I know for which tokenId? And what if the royalties are sent later on, and not directly at the time of sale?

aklos commented 3 years ago

If I'm understanding this correctly, then I agree with @dievardump. Creating a payable method that ties royalty to a tokenId sounds like the best way to do this. It allows people to verify that royalties are being sent, while allowing contracts and marketplaces to do their magic with the royalties (splitting to contributers, sending it all to the creator, deferring payment, etc.).

One issue I could see happen is that a marketplace might refuse to send royalties after agreeing to do so, but that will be easily verifiable and people would know not to use that marketplace anymore.

So, essentially a payable method, an information method, and an event would do wonders. Internal contract transfers (buying/selling) would be able to skip the payable method and directly process the royalty.

Percentage based royalties would probably handle most cases, but maybe it'd be best to allow the contract to return a royalty value in Wei when asking for royalty info by passing the amount paid for the token? (Obvious security hole there, but it's an idea)

wighawag commented 3 years ago

@aklos @dievardump My opinion on this is that the function that would allow you to keep track of the actual payment (which might no represent the full payment as marketplace are free to call it with the correct value or not) should be part of a separate EIP.

This allows us to focus on the minimum requirement (royaltyInfo) on this eip-2981 and let us explore different mechanism in another EIP to ensure accurate tracking of royalty being paid

As mentioned above, one possibility for the later is to do it through a singleton contract to allow NFT contract that implement eip-2981 (which would only prescribe royaltyInfo and nothing else) to benefit from it, without the need for them to implement anything. basically Marketplace would call this singleton contract, which in turn will call royaltyInfo and ensure the payment made through it (ETH but also ERC20) is correct.

@seibelj Regarding percentage, I kind of like what @lenifoti suggest as it allow more creativity without much more complexity. You could implement different curve for royalty or max cap for example. The function would still be a view function.

dievardump commented 3 years ago

@aklos @dievardump My opinion on this is that the function that would allow you to keep track of the actual payment (which might no represent the full payment as marketplace are free to call it with the correct value or not) should be part of a separate EIP.

I am not sure to understand this sentence. We really need a way to (1) link royaltyValue and tokenId (2) verify that royaltyValue has actually been sent How can we make this in a separate EIP, since this one is the one declaring how to get the value and to whom send it?

if we do not enforce (1) and (2) in this EIP, then it's a simple getter and marketplaces will have to do even more introspection to find out what to do with the value.

We should really keep everything short and in one eip if we want marketplaces to implement it.

Regarding percentage, I kind of like what @lenifoti suggest as it allow more creativity without much more complexity. You could implement different curve for royalty or max cap for example. The function would still be a view function.

I also prefer the idea to do royaltyInfo(tokenId, saleValue) because royalties can be capped to a maximum amount (even legally) and it would be a good way to add more royalties mechanisms.

wighawag commented 3 years ago

@dievardump

first of all, marketplace consideration of royalties will always be voluntary.

With the 2 EIP setup I imagine (EIP-2981 being only focused on royaltyInfo), marketplace will be expected to do something like the following in their Sale contracts to handle payment to seller and royalty recipient:

function purchase(....) {
  ...
  uint256 amountLeftForSeller = royaltySingletonProcessor.payRoyalties(erc721AndEip2981Contract, tokenID, erc20, price, payer);
  erc20.transferFrom(payer, seller, amountLeftForSeller );
  ...
}

The royaltySingletonProcessor specification is expected to be agreed on as the result of a new EIP. that contract will do something like

function payRoyalties(EIP2981 tokenContract, uint256 tokenID, ERC20 paymentToken, uint256 price, address payer) external returns (uint256 amountForSeller) {
  (address royaltyRecipient, uint256 royaltyAmount) = tokenContract.royaltyInfo(tokenID);
  uint256 tokenAmount = price * royaltyAmount / 100000;
  erc20.transferFrom(payer, royaltyRecipient, tokenAmount);
  emit RoyaltyPaid(tokenContract, tokenID, royaltyRecipient, tokenAmount, payer);
  amountForSeller = price - tokenAmount;
}

This was written haslty and without support for ETH payment that we will want to support, but this should showcase the overall idea.

It is true that without that singleton contract EIP ready, the flow with only EIP-2981 will be different. It will not handle the tracking of the payment.

it will only be something like :

function purchase(....) {
  ...
  (address royaltyRecipient, uint256 royaltyAmount) = tokenContract.royaltyInfo(tokenID);
  uint256 tokenAmount = price * royaltyAmount / 100000;
  erc20.transferFrom(payer, royaltyRecipient, tokenAmount);
  erc20.transferFrom(payer, seller, price - tokenAmount);
  ...
}

The point to separate the 2 EIP though is that the singleton contract is much more complicated to agree on and might not even be the right approach. Other in this thread have different ideas.

But having royaltyInfo first has some great advantages.

For example NFT contract can start to be implemented to support it, knowing that a future EIP will be able to handle the tracking.

And it is much easier to make marketplace update their sale contract (that can easily be swapped with a new one) than the NFT contracts themselves.

VexyCats commented 3 years ago

No. Every tokenId has an history, including collaboration, amounts going to associations, etc etc... taxes is just one of the problem. We need, in this EIP, to enforce a way to link tokenId and royaltyAmount, and to verify that this amount has actually been sent. Else , it's broken.

We cannot make implementation of checking the amount has been sent in an EIP - that is not what the scope of EIPs cover. They do not cover implementation of logic, atleast this one does not.

If you are concerned with recieving false calls to the onRoyaltiesReceived function - then the creator would simply add a check require(msg.value === _amount).

Are you a Solidity developer? This doesn't work as it stands. We would have to make the function payable. But then anyone doing this check would break any sale with royalties that are not send with the call to this function. This is why it MUST be enforced.

Yes, and its pseudo code I'm writing quickly to give an example of my thoughts.

I literally said a good middle ground to do what you want to achieve would be making that specific function payable.... But you haven't seemed to mention that?

And asking marketplaces to add things like if(receiver === nftAddress){ is not a viable solution, because the more you complicate the implementation they have to make, the less they will support it. that's why asking them to send over, and that's it, is a loooooot easier.

Trying to find a middle ground between making things easy on the marketplace and making things easier on the user as well.

People shouldn't have to check every transactions that have been made to them, to try to find why they had random money sent over.

I don't understand this logic. Literally looking at the transaction on etherscan would show exactly what token was sold, for how much, and how much they received. It would be even harder to track what payments were made to them if they withdrew their money from an NFT contract once a month. Talk about headache with taxes.

If someone sell one of my piece on a marketplace with a contract that is not verified, and the I can't actually see what happened during the transaction, how am I to know this was royalties? and how do I know for which tokenId? And what if the royalties are sent later on, and not directly at the time of sale?

An ERC721 transfer is shown on etherscan regardless if the contract is verified.

Why/how would royalties be sent a later time? The only way that happens.....is if the contract manages the royalty payments and the marketplace sends funds to the NFT contract, and then they would need to withdraw their funds.

VexyCats commented 3 years ago

I am not sure to understand this sentence. We really need a way to (1) link royaltyValue and tokenId (2) verify that royaltyValue has actually been sent

Can you give me an example of how you propose this EIP to verify that royaltyValue has been sent - by following the rules of EIPs (meaning no implementation logic for this).

I also prefer the idea to do royaltyInfo(tokenId, saleValue) because royalties can be capped to a maximum amount (even legally) and it would be a good way to add more royalties mechanisms.

This is something I think is a good idea as well - but we would need to also pass in the ERC20 contract address for ERC20 payments....

VexyCats commented 3 years ago

@aklos

So, essentially a payable method, an information method, and an event would do wonders. Internal contract transfers (buying/selling) would be able to skip the payable method and directly process the royalty.

I agree with this as well. Enforcement is not really an option and this provides the best means of a set of functions that allow for all the use cases.

Lenoftawa commented 3 years ago

@VexyCats

So - actually I think a good middle ground here is simply that - add payable to the function [onRoyaltiesReceived] and thats it. What do you think about this?

A good middle-ground, but I'm not in favour. I agree with the need for this behaviour but I think it needs to be part of a different interface spec (let's call it RoyaltyRecipient). The marketplace has to call a special function when sending the funds, but it does not need to know whether it is the NFT or another contract that is handling it - only that it supports the RoyaltyRecipient interface.

I'd also like to explore @wighawag's singleton idea, but again, not in this EIP.

I prefer to nail down the minimal functionality that we can agree on. I believe that would need to exclude the payment and the event. I don't see this ever seeing the light of day otherwise.

Can we limit this EIP to the following, and no more?

  1. it must be fully compatible with ERC-721 and ERC-1155
  2. it must allow the creator to specify a percentage royalty to pay and the marketplace to discover it
  3. it must allow the creator to specify a recipient for the royalty, and the marketplace to discover it
  4. it must not constrain the protocol between the marketplace and the recipient.
  5. it should be sympathetic to extensions that allow other ways of calculating the royalty based on sale price and environmental data.

Given this, a marketplace can charge the fee and send it to the recipient, or keep it and allow only the recipient to claim it. This is pretty loose, but it must be better that the current systems.

So we can gauge the level of consensus we have, can contributors please vote with thumbs up or down on limiting this EIP to the statements 1-5 above?

Lenoftawa commented 3 years ago

@dievardump

The only way for this to work, would be if the royalty value is sent with the call....

OK

...and then processed by the nft contract.

It's is this part of the statement that I think is unnecessarily limiting. In my mind, there is no reason that another contract could not process the payment. As you point out that the payment would need to include the index (and NFT address). This contract would then be responsible for all the processing and emitting the event (if it wished to).

That's why I see a RoyaltyRecipient as an interface (in a separate EIP) that is distinct from the ERC-2981 interface. It seems more logical to me for the royalty recipient contract to expose this interface, but it could also the NFT itself.

We are really designing a protocol for interaction between contracts that support various interfaces. The protocol should ideally be agnostic of how interfaces are mapped to contract addresses.

aklos commented 3 years ago

Seems to me that the ecosystem is still too immature to be making an informed decision on what exactly royalty standards should look like. That said, creating an EIP that only implements 1/4 of a standard seems like a waste? Not sure, but if there are previous examples of this approach working then I'm all for it.

dievardump commented 3 years ago

It's is this part of the statement that I think is unnecessarily limiting. In my mind, there is no reason that another contract could not process the payment. As you point out that the payment would need to include the index (and NFT address). This contract would then be responsible for all the processing and emitting the event (if it wished to).

The problem is it makes it even more difficult if the marketplace gets the recipient, and needs to check if it's a contract or not.

That's why, if the value is sent directly to NFT contract, everything can happen after, and the code that the marketplaces will have to implement is the most straightforward. (1) get royalty value (2) send royalty value over, and that's it.

If we want the EIP to be implemented by marketplaces, we need to make this as less code as possible for them, and as less headache as possible. that's why sending it to the NFT contract is the easiest for everyone

If we make it so marketplaces must check if the receiver is an EOA or not, if it implements this or that... it won't work.

Let those check be done by the NFT contracts. It is the contract saying "I want royalties", it is the one that should handle those, however it wants.

Can you give me an example of how you propose this EIP to verify that royaltyValue has been sent - by following the rules of EIPs (meaning no implementation logic for this).

(mimicking the Safe Transfer Rules in ERC1155) :

Royalty Payment Rules

Scenarios

Scenario#1 : The call to royaltyInfo returns 0

There is no need to send anything over

Scenario#2 : The call to royaltyInfo returns a value greater than 0

The contract MUST call onRoyaltiesReceived on the NFT contract

Rules :

onRoyaltiesReceived rules:


The reason why ERC1155 works so good, is because it enforces a lot of things. We can do the same, without implementation, only rules, scenarios and "MUST".

dievardump commented 3 years ago

I don't understand this logic. Literally looking at the transaction on etherscan would show exactly what token was sold, for how much, and how much they received. It would be even harder to track what payments were made to them if they withdrew their money from an NFT contract once a month. Talk about headache with taxes.

Not with (1) verification mandatory at time of royalties payment (2) an event that reflect the payment. Then either the platform controlling the NFT contract can show all users a very easy log of the events Either the artist is techy and can get the events very easily.

Also not every ethereum based chains have etherscan. Blockscout is cool but... well... it's a bit of a mess for those things.

An ERC721 transfer is shown on etherscan regardless if the contract is verified.

Payments can be done on a different transaction than the transfer itself. Which means that the royalty payment would be done at a different time.

What about 5 tokens sold in the same transaction and 5 royalties transfer, but no way to know what transfer is for what token? (yes this matters. for splitting, for example)

Why/how would royalties be sent a later time?

The question is not why/how, but "can that happen?". The answer is yes, because none of us has all the use case in head. It can happen, let's make it so what we "standardize" right now, takes this into account.

dievardump commented 3 years ago

I'd also like to explore @wighawag's singleton idea, but again, not in this EIP.

I'm for the singleton idea too, I've talked about it early in the discussions here, and I've been talking about it with different people on discord too, we probably will go for an EIP. if you're interested hit me up dievardump#0964.

Lenoftawa commented 3 years ago

@dievardump

The problem is it makes it even more difficult if the marketplace gets the recipient, and needs to check if it's a contract or not.

True, the marketplace needs to check that the address is a contract that supports the RoyaltyRecipient Interface. But it wouldnt it also want to check that the NFT supports the ERC-2981 interface before it called onRoyaltiesReceived()?

The singleton would fix that though? It would effectively do the check?

Lenoftawa commented 3 years ago

Payments can be done on a different transaction than the transfer itself. Which means that the royalty payment would be done at a different time

We should make the assumption that this is the case, so it seems sensible for any event to log the tx hash.

Just the fact the onRoyaltiesReceived event is named using the past tense suggests that it should be emtted after the payment has happened, so the tx id will be known.

dievardump commented 3 years ago

True, the marketplace needs to check that the address is a contract that supports the RoyaltyRecipient Interface. But it wouldnt it also want to check that the NFT supports the ERC-2782 interface before it called onRoyaltiesReceived()?

The marketplaces would only have to check for ERC-2782. then if ERC-2782 is supported, it would call royaltyInfo and then send over the royalties. And that's it.

The marketplace will have to check for ERC-2782 before calling royaltyInfo. But then it already knows that 2782 is supported, so it can call onRoyaltiesReceived

The singleton would fix that though? It would effectively do the check?

The singleton is a different way of doing this. but let's not talk about it here

jamesmorgan commented 3 years ago

Hi all,

I still believe this EIP needs to be slim and simple and let payments/splitting be handled by another standard.

The goal of this was really to make a simple, gas efficient way for marketplaces to query and know where and what to send in terms of royalties when a sale is made on their marketplace. Not necessarily the enforcement of them which should probably be its own new standards.

I also still have a hard time understanding the true benefit of onRoyaltiesReceived since it does not enforce which is why I favour making it OPTIONAL as there will need to be a off-chain or secondary reconciliation process to know if the true payment has been made. Or removing it and spinning out another EP which has this verification and event mechanic listed.

I am not keen on sending money to the NFT contract itself as it makes life much easier when the core NFT contracts are shared in terms of minting so they are used by many users. Also it make its hard as the NFT contract may need to also deal with logic around NFTs which have multiple creators etc. So I for me I believe royaltyInfo returning a recipient and amount is all that is really needed in this EIP for it to be used to better the ecosystem as it currently stands.

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

This one method will provide the following:

  1. A simple way for marketplaces to know where to and what to send. Something which atm is a big problem due to the fractured standards about token creator and token royalties recipient in the NFT industry.
  2. Is fully compatible with ERC-721 and ERC-1155 standards
  3. Separates the handling/splitting of any royalties from the actually ability for marketplaces to provide them in the first place, this is a big step forward
  4. As its only an address, allows for richer more collaborative tools to built which sit behind the receiver address e.g. this could be a Gnosis Multi-sig and then bingo we have DAO managed royalties (as an example only)

It would be great to get some form of consensus on the scope of this EIP and try get it moved from draft. We can then push for marketplaces to adopt and start work on more advanced enforced royalties.

Let me know what people think is left for this to move forward.

wighawag commented 3 years ago

Thanks @jamesmorgan for summarizing a proposal.

I also still have a hard time understanding the true benefit of onRoyaltiesReceived since it does not enforce which is why I favour making it OPTIONAL as there will need to be a off-chain or secondary reconciliation process to know if the true payment has been made. Or removing it and spinning out another EP which has this verification and event mechanic listed.

I am strongly against having onRoyaltiesReceived as part of this standard. Let's keep it simple. Plus optionality is not great for standards and would basically indicate that it is a separate standard.

I am not keen on sending money to the NFT contract itself as it makes life much easier when the core NFT contracts are shared in terms of minting so they are used by many users. Also it make its hard as the NFT contract may need to also deal with logic around NFTs which have multiple creators etc. So I for me I believe royaltyInfo returning a recipient and amount is all that is really needed in this EIP for it to be used to better the ecosystem as it currently stands.

I strongly agree. The less we put in the NFT contract, the better.

I like

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

but @lenifoti's proposal allows more flexibility and I would prefer that unless there is a clear disadvantage. It also fulfil the 4 points mentioned.

Something like

function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address receiver, uint256 royaltyAmount);

where value is the purchase value and royaltyAmount would be the amount to be paid to the royalty recipient. (bonus: no need to deal with percentages)

ghost commented 3 years ago

Strong agreement that the only thing in this EIP should be royaltyInfo.

About

function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address receiver, uint256 royaltyAmount);

What I don't like about this is the royalty percentage could be different depending on how much you are paying, which is really weird. Royalty percentages in the industry are standard flat rates. Variable payments are not used anywhere.

I feel like this is a convenience function that could be optional, but we need to return a flat royalty percentage for the NFT.

wighawag commented 3 years ago

thanks @recur-it for your comment

What I don't like about this is the royalty percentage could be different depending on how much you are paying, which is really weird. Royalty percentages in the industry are standard flat rates. Variable payments are not used anywhere.

If we think this is standard, why worry it will be used differently ? if it is used differently, then it might because there is actually interest for it. In other word: why restrict ?

It could be used for different curves for example. It could be used to implement a cap too.

jamesmorgan commented 3 years ago

@wighawag thanks for the quick response.

I also favour only having this EIP as the royaltyInfo hook and moving the event and verification of funds to another EIP but I am unsure if this is "done thing" for such changes.

You mentioned the additional of a value field which kind of makes sense but what happens if the payable type of ERC20 etc, does this need to also be aware of decimals etc etc which could be messy. Any ideas on how to handle this?

function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address receiver, uint256 royaltyAmount);

The beauty of the previous percentage is that it left the handling of payment type e.g. ETH/ERC20 to the caller but maybe I am mistaken and it's not that difficult to handle them together.

Passing in amount however may open this up to more interesting use cases such as stepped royalties depending on the sale amount and or curve based logic.

seibelj commented 3 years ago

You mentioned the additional of a value field which kind of makes sense but what happens if the payable type of ERC20 etc, does this need to also be aware of decimals etc etc which could be messy. Any ideas on how to handle this?

Does not need to be aware of decimals. If it's 6 decimals or 18 decimals, you pass in the full amount and it would return the portion of it owed as a fee. Completely unaware of whether its a ERC20, ETH, etc. should be fine.

I am starting to like this solution as it eliminates any arguments after-the-fact about incorrect calculations.

Example:

Fee is 10%
Token is USDC
Decimals of USDC are 6
Want to buy for 750 USDC
Call royaltyInfo(90210, 750000000) returns (0xdead..., 75000000)
Notice how the decimals don't matter - just returned 10% of the total
NightRabbit commented 3 years ago

Hi everyone. My name is Rabbit (LinkedIn). I am a computer scientist and a newbie DAPP developer. I am creating a marketplace called The Morpheus Project for mixed-reality NFT.

It seems that we are discussing about having a standard for the marketplaces to get royalty info. So that they can pay the fee to the creators. Then the marketplace becomes a centralized organization that has control over the exchange of NFT. It would go against the fundamental principle of DAPP. We created smart contract and cryptocurrencies to remove the middle man such as banks.

What if we take a different approach? I have a solution in mide, I hope you guys can share you opinion about it. Many thanks!

To recap that I understand correctly...

Issue 1: Metadata immutability and longevity The first issue is metadata immutability and longevity. Currently, a chain store URI links that point to an NFT's metadata. Therefore, the metadata can be on a private server, which means that it is centralized, prone to single-point-of-failure, and a server administrator can tamper with the data. To solve this issue, many people encourage the use of decentralized file storage systems. The systems increase the likelihood of NFT data survives long into the future, but it is not 100% guaranteed. There may be a situation that nobody in the network pinned a file, and the data is lost forever. There are paid-service that ensure the availability of data, but someone has to pay for the recurring cost to use the service. Not to mention that if someone can edit the URI link, then the metadata becomes immutable.

Issue 2: Royalty for Creator NFT has been one of the ways for Creator to monetize their work in the long run. However, it seems that not many people aware that the arrangement is not guaranteed. The royalty mechanism is not embedded directly into the NFT contract (ERC-721). Currently, the NFT marketplace is managing the transactions for creators. If an NFT owner chooses to sell his/her art on a marketplace that does not honour the royalty fee, the creators will get none of it. The issue seems not to be a big deal right now because large marketplace came together and jointly agree on the royalty mechanism. However, there is no guarantee of an emerging black market that sells NFT without honouring the agreement. There is an incentive for the NFT owner to sell in the black market as well. He/she will get to keep the royalty portion, which can be up to 10% or more.

Our approach to improving NFT

To address the metadata immutability and longevity issue, I propose the following modification to the ERC-721:

  1. Add backupURI (string) optional property: To ensure the longevity of NFT, I propose adding another property called backupURI (string). The current URI property is still in effect. It can be on a centralized server or a decentralized file storage system. However, the NFT owner can download the metadata, create a backup, post it on the internet and set the backupURI link as a safety measure. This option gives the power to the NFT owner to control and ensure their NFT existence, especially the highly valued ones.
  2. Change tokenID property's datatype from uint256 to bytes32 to store hash values of metadata: Instead of allowing the creator to generate an arbitrary tokenID, I propose using the hash value of metadata as a tokenID directly. This ensures that every NFT has unique metadata behind it. If metadata has tampered with, then the hash value stored on the chain and the calculated hash value from the metadata will no longer match, thus destroying the link between NFT reference on-chain and metadata off-chain. A client software/user can read the metadata from the default URI and check that hash values are the same. If they are, they can continue as normal. If the hash values are not the same, the client can use the backup URI provided by the NFT owner and compare hash values once again.

With these changes, NFT owner can take the necessary steps to ensure their NFT survivability. They may choose to pay for a decentralized storage service or use a centralized server to save cost. Meanwhile, everyone can be sure that the NFT metadata is correct.

To address the royalty for creator issue, I propose the following modification to the ERC-721:

  1. Add pricing mechanism to NFT: I propose that we add a new "prices[tokenId => uint256]" property and modify the transfer() to a payable function so that the selling price is a part of an NFT, and to enforce transactions to follow the agreement between a creator and an NFT owner.
  2. Replace NFT operators with selling agent: To do so, I propose that we remove the approve() and related attributes/functions. Then we replace them with an attribute that store the addresses of the selling agent and their commission fees sellingAgent[tokenId =>[ageent_address => uint256]].
  3. Total Price = Selling Price + (Selling Price X Agent commission fee): The selling price does not include the agent commission fee. Instead, the commission fee is added on top of the selling price. This approach enables the base price to be the same in every market. It is up to each market to set their commission fee and to compete with one another.
  4. Include creator address and publisher address in NFT: We add additional properties to store creators address, publisher address, creator royalty fee and publisher royalty fee ([tokenId => creator_address], [tokenId => creator_fee], [tokenId => publisher_address] and [tokenId => publisher_fee]). So that we can refers to these information when a transaction occured.
  5. The selling price includes creator royalty's fee and publisher's fee: Here, we set the agreement between NFT owner, creators and publishers in an NFT. We implement the transfer() function such that the creators and publisher receive a percentage of the selling price. We send the fees to the creators using send() function, so the creators can only receive the fees and cannot block the transaction.

Limitations and issues

Wrapping up

That's it! A brief overview of my solution. What do you think? I am new to this space. Please share your thoughts and suggestions. I hope that we can discuss together and find the best way to move forward together!

Lenoftawa commented 3 years ago

Welcome @NightRabbit. We have spent some time (as you can see from the length of this discussion) setting the scope. One thing that we agree is that enforcing a sale contractually is impossible because a seller and buyer can perform an under-the table transaction and report a nominal or zero fee.

What we can do is leave an audit trail to make more transparent and discourage non-compliant marketplaces through other measures.

We also agree that we should try and limit the scope of this EIP to just exposing the amount/percentage to pay and the address to receive the payment.

We don't see metadata storage as within the scope. We intend to solve the payment protocol in another EIP, so your proposal would likely be more relevant there. However, we have also agreed that we must be fully compatible with ERC 721 and ERC 1155.

Lenoftawa commented 3 years ago

Just coming back to this, I see that there is some agreement with a suggestion to allow more flexible calculation of the fee but also a recognition that we also need to present a percentage to fit in with the standard mechanism now, and allow the Marketplace to calculate the price without constantly calling the NFT contract. I'd be happy with both, and even to deal with the extension in another EIP, but binding the percentage to getRoyaltyInfo() seems wrong when there are potentially other ways of getting calculating the fee. For this reason, I'd prefer to simplify the functions into single purpose getters: getRoyaltyRecipient(index), getRoyaltyPercentage(index), getRoyaltyAmount(index,salePrice). Then we need to consider how the NFT signals what type of royalty it support, perhaps by returning an out-of-range value for the options that it does not support.

These are all gasless. The Marketplace can also cache them (except for getRoyaltyAmount()).

Thoughts?

Lenoftawa commented 3 years ago

I have created a number of pseudocoded sequence diagrams to represent some options. Here is the simplest, where the Marketplace emits the event. Note that as @dievardump (i think) pointed out, the RoyaltyRecipient would need to know the index, so we cannot simply send the funds to it - we are imposing an interface on it.

image https://sequencediagram.org/ title ERC-2981 interaction (a) participant Marketplace participant NFT participant Seller participant Buyer participant RoyaltyReceiver entryspacing 0.9 Marketplace-->NFT:royaltyReceiver = GetRoyaltyReceiver() Marketplace-->NFT:Percentage = GetRoyaltyPercentage() note over Marketplace,Buyer:Sale completed and settled in transaction TxID Marketplace<--> Marketplace:Calculate royalty (salePrice, percentage) Marketplace->RoyaltyReceiver:royaltyReceiver.send(NFT, index, royalty) Marketplace<-> Marketplace:emit onRoyaltyReceived(NFT, index, royalty, Seller, Buyer, TxID)

jamesmorgan commented 3 years ago

Thanks for putting that together @lenifoti , a few thoughts/questions come to mind when looking over the tweaked proposal.

  1. Are you suggesting that the EIP needs to be split into two methods - one for getting the receiver and one for getting the percentage? If so why not do this in a single call? (maybe I miss understand the comment)

  2. What is the reasoning for enforcing an interface on the receiver, specifically about the need for a index?

  3. I thought there was broad agreement on the problems with onRoyaltiesReceievd event and this would not be included?

This is a simple flow of what I expect marketplaces to do with the current simple royaltyInfo() lookup:

sequencediagram.org

title ERC-2981 interaction (a)
participant Marketplace
participant NFT
participant Seller
participant Buyer
participant RoyaltyReceiver
entryspacing 0.9
Marketplace-->NFT:royaltyInfo(tokenId) = (receiver, %)
Marketplace-->Marketplace:Maretplace commission determined and taken
note over Marketplace:Marketplaces will most likely have a max\n supported royalty % and cap the %\nreturned from the royaltyInfo() call
Marketplace<--> Marketplace:Calculate royalty (salePrice, [percentage || max])
Marketplace-->Seller:Seller payment made (marketplace commission - calculated royalty)
Marketplace-->RoyaltyReceiver:receiver.send(royalty/ERC20 amount)
note over Marketplace,Buyer:Token ownership changed once all funds are settled

image

The mention of a marketplace cap is just a guess and a thought I have had recently. This is mainly driven from the fact that marketplaces are businesses and would they be happing applying a 100% royalty, this may be a hard sell for them. Just an idea though and I could be completely wrong.

Lenoftawa commented 3 years ago

@jamesmorgan Separation of the functions came from the idea that there is more than one way to calculating the Royalty. One requires an extra argument so combining them would be clumsy, but I'm open to suggestions. Having one included in getroyaltyinfo() and the other in a new function seems counterintuitive. Splitting them out just seemed to make the interface cleaner.

Yes. The event should not be part of this EIP. I was just working with options. When you treat this as a protocol the question about who does what becomes interesting.

If the royaltyReceiver is also the NFT then it would need to know the index. The marketplace does not know what the contract is, then it needs to give it enough information to uniquely identify the NFT being sold. The other option is that the NFT returns an id of some sort. But we already have the index. Also, I had been playing with the idea of the royaltyReceiver emitting the event.

dievardump commented 3 years ago

This is a simple flow of what I expect marketplaces to do with the current simple royaltyInfo() lookup:

I think this is where we're going. Although for me not optimal for reasons I cited before, that's probably what we will go for.

What about the idea of sending the selling price with the call to royaltyInfo(tokenId, sellPrice) (that was mentioned in the above comments), and let the NFT contract return a (recipient, valueAskedFor) instead of a %age?

That would actually be less prompt to errors with the "Fixed percentage to 10^5 (100000)" and allow more flexibility in calculation for the NFT contract (when there is variable royalties for example)

The Marketplace could still easily cap the valueAskedFor (like in your flow)

Lenoftawa commented 3 years ago

@dievardump Your proposal is how I originally envisioned it, however, the objection was that it makes things more complex and unpredictable for marketplaces in the most common case. I accept that argument and think the best way is to accommodate both. Thus the proposal to separate out the concerns of identifying the receiver vs determining the royalty.

Lenoftawa commented 3 years ago

One thing that has been mentioned but not resolved is how the token type is determined. ETH would be a fair guess and could be sufficient for this EIP. However, ERC-20 is likely to be common.

We could load up the NFT with more responsibilities associated with royalties when perhaps it is more appropriate for it to fall on the recipient. Thus we could limit the extension to the NFT interface to getRoyaltyRecipient() and stop there. The marketplace would query the RoyaltyRecipient for the royalty and token type before sending the fee to it. That does not stop the NFT from nominating itself and forwarding the fee.

This then creates 2 interfaces, One must be supported by the NFT and the other by any contract. As I have said, I believe that a full solution is a protocol describing interactions between new interfaces and not just an extension to ERC-721.

I am not proposing extending the scope, simply considering that this could be 2 interface specifications that allow the implementer more freedon in how responsibilities are partitioned across contracts. image Note that I'm not suggesting that we need to include the token type in this EIP.

wighawag commented 3 years ago

I don't think adding more function just to get the resulting amount in different ways is a good idea, if we can have one function that cover all case, then we should use that.

The function mentioned above:

function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address receiver, uint256 royaltyAmount);

handle percentage and other curves for example.

And having one method is much simpler both for NFT implementer and marketplaces.

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.

dievardump commented 3 years ago

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

wighawag commented 3 years ago

@dievardump you're right, and it might well be useful in practise.

I like the current design as it is not even dependent on the token standard used to pay. Purchase could be made in ERC1155 and it would still work.

But having native support for ERC20 could indeed be beneficial. I am personally undecided.

Another consideration is whether we should allow NFT to advertise a specific token for the royalty to be paid in, regardless of the token used to purchase. Personally I think it will introduce too many complication for the marketplace (they need to ensure users have approved it) that it is not worth it, but while we are discussing the potential avenues, I though it worth mentioning.

seibelj commented 3 years ago

This should focus on how much to pay (royalty percentage) and who to pay (address of wallet or contract). The mechanism of payment, payment notifications / events, which tokens are supported for payment, etc. we have been saying should go in an alternative EIP so there can be multiple different implementations of payments as we cannot agree to it.

As such, I don't see why which token matters for getting the royalty amount and that should not be included here.

As I previously stated, I like the idea of passing in the total payment and receiving the royalty amount to pay. We discussed before that some might like to return a different royalty percentage based on the amount paid, which is IMO a really strange use-case, but if we go that route then we should not return a flat percentage as that would potentially be different for each amount paid, so we don't want a separate function for retrieving a percentage.

Therefore, only function this EIP would need to have is:

royaltyInfo(tokenId, totalPaid) returns (royaltyPaymentAddress, royaltyAmount)

Example:

royaltyInfo(90210, 750000000) returns (0xdead..., 75000000)

Marketplace could work backwards and figure out this is a 10% fee, or just pass in 10000 and see what comes back, etc.

Lenoftawa commented 3 years ago

To be absolutely clear, I would not want to lose the ability to support complex royalties. I think we are in agreement that we should support that - let's lock that requirement in.

The question for me is: "what do we lose if we don't allow the marketplace to know if the percentage is constant".

@seibelj says:

Marketplace could work backwards and figure out this is a 10% fee, or just pass in 10000 and see what comes back, etc.

Reverse calculation gives the marketplace the percentage for the specified sale price, but the marketplace cannot cache that percentage. The negatives I can see are:

Is it sufficiently important that we need to allow marketplaces to discover whether the royalty is a constant %age?

If we decide that it is sufficiently important, then how do we signal this to the marketplace?

@wighawag says:

if we can have one function that cover all case, then we should use that.

I believe this is referring to having multiple ways of exposing the royalty. I suggested separate functions, but it could be returned as a flag, or a special value range...

The other split was between getting the amount/percentage and getting the RoyaltyReceiver. Did you have views on that?

Moving the responsibility for negotiating the calculation (and then the payment) of the royalty to the RoyaltyReceiver seemed like a valid separation of concerns. I guess the NFT can always present a facade and forward to another contract as opposed to returning a contract address and washing its hands.

wighawag commented 3 years ago

The question for me is: "what do we lose if we don't allow the marketplace to know if the percentage is constant".

I see, that is a different concern than I imagined. Note thought that the original royaltyInfo was not guaranteeing constants neither.

If we want to allow relative constant, we should use events instead. But I have hard time imagining the fact that it will cause issue if marketplace have to call the contract. I always imagined they would call it on-chain so they can make it automatic. see below though:

Ability to display the royalty as a percentage, even though that's almost certainly the case.

This is a good point. but again if this changes over time, like the original royaltyInfo they still need to have it updated at the time of purchase and they can ensure a certain guarantee by fetching it off-chain before the purchase happen on-chain.

I believe this is referring to having multiple ways of exposing the royalty. I suggested separate functions, but it could be returned as a flag, or a special value range...

I would prefers something simpler than flags. But as mentioned above the issue is more about "constant" than whether we use percentage or a more flexible solution. And for constant, the best mechanism would be to use event but this is not practical when we have a royalty amount per tokenId. So my thinking is that we should forego the idea that royaltyAmount are guaranteed to be constant. Marketplace will have to consider it non-constant, but as the act of paying royalty is voluntary they are free to fetch the new rate at their convenience. I expect most NFT contract to have a constant function for computing the rate anyway.

The other split was between getting the amount/percentage and getting the RoyaltyReceiver. Did you have views on that?

I mentioned this splitting in an earlier conversation in this thread and I like it, but I have no strong feeling unless we have a strong use case for it.

seibelj commented 3 years ago

Is it sufficiently important that we need to allow marketplaces to discover whether the royalty is a constant %age?

If we decide that it is sufficiently important, then how do we signal this to the marketplace?

You cannot have it both ways - either it is constant, or dependent on the amount paid. If dependent on the amount paid, then it's possible that every single amount is 10% but if the amount is 7777777 (or any other number) changes it to 90%. You simply cannot introspect it feasibly without reviewing the smart contract itself. I assume 99% of all contracts will use a fixed percent and then when you pass in an amount it just does the calculation for you. If you want fixed, then it cannot be dynamic. If you want dynamic, it cannot be fixed, and the overall royalty percentage cannot be assumed.

Lenoftawa commented 3 years ago

You simply cannot introspect it feasibly without reviewing the smart contract itself.

True. It is possible that the amount returned is not be related to the sale price at all! It could be a fixed amount or time-based for example. An interface that only returns an amount gives NFTs a lot of freedom, and from very bitter experience, developers will find ways of using this freedom in ways we can't imagine.

At present marketplaces just use a percentage. However, I suspect that's because there is no way for NFTs to express anything else (probably because the marketplaces are in control).

So, even if a constant percentage is the most common case, does the marketplace need to know when it isn't?

What I'm hearing is: "No, the market place must always assume that it isn't a constant percentage, and there is only one way to discover the royalty amount: call the contract at the time of sale". This needs to be made very very clear in the EIP.

(Len of Tawa#0246)

Lenoftawa commented 3 years ago

@wighawag

If we want to allow relative constant, we should use events instead.

Yes, if "relative constant" means "constant percentage", then this this is the case I'm referring to.

How do you see an event working - would the event be emitted when the NFT is minted? Any other time would consume extra gas, whereas a function call is gasless.

wighawag commented 3 years ago

@lenifoti when I refers to "relative" constant, I meant that if we use event for advertising royalties, then it will be constant enough. as an event can easily be cached and the value updated (when a new event comes in)

But I don't think we should go that route anyway.

We should simply make it so that marketplace cannot assume the value returned will not change the next block (whether it is a percentage or a more flexible value does not matter). And I agree that it would be good to make it clear.

Lenoftawa commented 3 years ago

I mentioned this splitting in an earlier conversation in this thread and I like it, but I have no strong feeling unless we have a strong use case for it....

Do you mean this statement:

We should split royaltyInfointo creatorOfand royaltyAmountOf.

I think we agreed it should be 'royaltyReceipient' not createrOf - right?

What I was showing was not just separating recipient and amount, but making the recipient responsible for returning the amount. So the marketplace would write something equivalent to:

recipient= nft.getRoyaltyRecipient() amount = recipient.RoyaltyAmount(salePrice) /* transfer token, pay royalty and emit event */

.... but I have no strong feeling unless we have a strong use case for it.

There's a challenge.

Cardinality is one thing I can think of. If the NFT can delegate the handling of all royalty to something else, then we break the 1:1 relationship and many NFTs can send to the same Royalty recipient. Of course it can do that by providing a façade, so maybe that's not compelling.

It reduces the amount of extra complexity loaded on top of what is already a complex NFT interface (1155 in particular).

It is the first step in recognising that the RoyaltyRecipient might need to be a separate interface anyway. For example, if at some point we allow the recipient to receive multiple currencies and the amount is not a percentage, then the amount to be paid will depend on the currency. If we keep to the combined interface proposal, we'll need to keep piling more onto the NFT interface.

It also leave the option open for any other contract to use as a means of collecting royalty.

Do these add up to a strong case? I'm more than happy to accept a considered and resounding "No".