rmrk-team/rmrk-spec

RIP-009: "Issuer-owned" Markets

jam10o-new opened this issue ยท 9 comments

  • author(s): @joshua-mir
  • contact: @Jam10o on twitter, tg, @Jam10o:matrix.org
  • RIP type: upgrade

RIP Summary

An extension/modification of the Buy and Mint interactions to allow issuers to specify a proportion of the value of the operation that must be paid directly to the current(?) issuer and previous owners of an NFT on purchase.

RIP Details

Issuance-side

An OPTIONAL additional field to the minified JSON for the MINT interaction - for the creator reward, or cr

1.0.0 Json:

{
  "name": "Dot Leap Early Promoters",
  "max": 100,
  "issuer": "CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp",
  "symbol": "DLEP",
  "id": "0aff6865bed3a66b-DLEP",
  "metadata": "ipfs://ipfs/QmVgs8P4awhZpFXhkkgnCwBp4AdKRj3F9K58mCZ6fxvn3j"
}

and post-upgrade:

{
  "name": "Dot Leap Early Promoters",
  "max": 100,
  "issuer": "CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp",
  "symbol": "DLEP",
  "id": "0aff6865bed3a66b-DLEP",
  "metadata": "ipfs://ipfs/QmVgs8P4awhZpFXhkkgnCwBp4AdKRj3F9K58mCZ6fxvn3j", 
  "cr": [10]
}

post-upgrade with additional rewards for "curators":

{
  "name": "Dot Leap Early Promoters",
  "max": 100,
  "issuer": "CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp",
  "symbol": "DLEP",
  "id": "0aff6865bed3a66b-DLEP",
  "metadata": "ipfs://ipfs/QmVgs8P4awhZpFXhkkgnCwBp4AdKRj3F9K58mCZ6fxvn3j", 
  "cr": [3, 1, 1, 5]
}

A value's position in the array of cr represents a Percent value of any Buy operation that should be sent to a historical owner of an NFT, and the final element represents the percentage that should be sent to the current issuer.

Both examples above represent 10% of the sale price of an NFT being sent to accounts other than the seller, but the first example represents 10% going entirely to the issuer, and the second represents 5% going to the issuer, with 3%, 1%, and 1% going to 3rd, 2nd, and immediate subsequent owners of the NFT prior to the current seller.

A quick hint for implementors to use when calculating which accounts are represented by which element in the cr list is (pseudocode):

// every historical owner of an NFT is prepended to 
// "owners" upon COMPLETION of their purchase
// ie, owners[0] is current owner of the NFT 
// elementIndex = index within `cr` to lookup
ownerAge = (cr.len() - elementIndex) - 1
if ownerAge > 0
  historicalOwner = owners[ownerAge]
else
  historicalOwner = issuer

Purchase-side

Currently, as of the 1.0.0 spec,
To complete a purchase, we submit a utility.batchAll call comprised of two transactions:

utility.batchAll([
  system.remark(
    0x726d726b3a3a4255593a3a312e302e303a3a353130353030302d306166663638363562656433613636622d56414c48454c4c4f2d504f54494f4e5f4845414c2d30303030303030303030303030303031
  ),
  tx.balances.transfer(CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp, 10000000000),
]);

where CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp is the current owner of an NFT.

This upgrade would require an additional check - did the collection have a non-zero cr when minted - and if this check is true, the sale amount is split between the seller and the issuer - in practice, this means that an additional transfer should be added to the batch.

An example of the above purchase with cr of 10, and issuer H9eSvWe34vQDJAWckeTHWSqSChRat8bgKHG39GC1fjvEm7y:

utility.batchAll([
  system.remark(
    0x726d726b3a3a4255593a3a312e302e303a3a353130353030302d306166663638363562656433613636622d56414c48454c4c4f2d504f54494f4e5f4845414c2d30303030303030303030303030303031
  ),
  tx.balances.transfer(CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp, 9000000000),
 
 tx.balances.transfer(H9eSvWe34vQDJAWckeTHWSqSChRat8bgKHG39GC1fjvEm7y, 1000000000),
]);

From the perspective of the purchaser, every element in the cr array represents an additional balances.transfer call to add to their batch, with the address of the recipient being a historical owner of the NFT.

Note that the proposed standard is to take the amount out of the stated listing price, and not add a fee on top of it - this is to prevent a purchaser paying more than the stated price for a listing.

Examples

I am creator, I create a collection and state that sales of NFTs within the collection are only valid when 1% of the sale price is sent to me as well as the current owner.

When someone buys an NFT that I am an issuer of, that is using this mechanism, they add an additional extrinsic to the batch with my cut.

So if I create a highly sought-after artwork, I will have a constant source of income - or if an artwork I created eventually gets sold for a very significant amount, I benefit from the appreciation.

Open Questions

should this be something modifyable by issuer? how would they do so?

should this be something that always goes to the "real" creator of a collection, or should this respect changeissuer?

what degree of precision should cr allow? (example above is Percent, but it may also be Permill, Perbill, represented by a decimal/floating value, etc.)

Should non-purchase transfers be disallowed to prevent out-of-channel sales of an NFT?

Prior work (optional)

Zora.co

Impact

This would be a backwards-incompatible change, so implementers should not display listings (for purchase) that use this feature - ie, they have a cr field in the collection - unless they have implemented it themselves. This should be easy to determine and filter for, however.

Tools would have to be modified so that consolidator may keep track of cr.len - 1 historical owners of an NFT.

Good stuff, but also good questions to answer before implementing.

to answer the open questions I laid out in the way that I believe they should be answered:

should this be something modifyable by issuer?

No - it should be something akin to the supply of the token - a part of the nature of the NFT, and the creator should be the one to determine their reward on minting, future issuers should not have the ability to change this value either, for the same reason (it may change the nature of the work).

Incidentally if this is a feature creators do indeed want to have available to them, I'd suggest either upgrading the changeissuer action to include a new optional cr value as well.

should this be something that always goes to the "real" creator of a collection, or should this respect changeissuer?

Should respect changeissuer, and in the future perhaps we should have methods to List issuance of a collection for creators to be able to "sell" the revenue stream.

what degree of precision should cr allow? (example above is Percent, but it may also be Permill, Perbill, represented by a decimal/floating value, etc.)

Percent is sufficient imo - at more precise values, the transaction fees for purchasing low price NFTs will start becoming higher than the actual payment to the issuer.

Zora rewards both the creator and the previous owner of an NFT - this may be something to consider for future extensions.

It may be possible to overload/modify the cr field in future to declare this sort of behavior (ie, a list of percent values, instead of a single percent value) - although I'm not sure about the implementation complexity - Would perhaps require looking up N previous owners of an NFT, and creating N transfers per BUY, given a list of n+1 values (first value always referring to the creator). Should probably not be included in any first implementation of this, but this should be up to you (@Swader) and other implementors (cc @yangwao) to determine if it's worth the extra implementation complexity.

No - it should be something akin to the supply of the token - a part of the nature of the NFT, and the creator should be the one to determine their reward on minting, future issuers should not have the ability to change this value either, for the same reason (it may change the nature of the work).

I agree.

Should respect changeissuer, and in the future perhaps we should have methods to List issuance of a collection for creators to be able to "sell" the revenue stream.

Sounds good to me.

Percent is sufficient imo - at more precise values, the transaction fees for purchasing low price NFTs will start becoming higher than the actual payment to the issuer.

You mean because the added byte weight of the extrinsic will outweigh the fee? We should also keep in mind the possible ED issues.

There is another consideration - nothing prevents someone from wrapping the token to avoid these sales, and then trading the wrapper. Finally unwrapping when ready to claim.

nothing prevents someone from wrapping the token to avoid these sales, and then trading the wrapper. Finally unwrapping when ready to claim.

A wrapped version of an NFT feels like "this is in the process of being traded", because presumably the "owner" of the NFT will be the wrapper for the period of time that is going on, and then once it gets unwrapped, it finally gets transferred to the real owner. This kind of explains the "reward the previous seller upon next sale" in Zora because it gives every owner of the wrapped NFT incentive to unwrap - I'm just reluctant to add that to the spec atm because I'm not sure how difficult it is to retrieve a list of previous owners of an NFT.

edit: did it.

For simplicity, I would propose the following upgrade:
Introduce new interaction RMRK::ROYALTY

How would it work?
If you in the process of minting NFT you can add royalty by default as @joshua-mir proposed.
When is NFT minted and the user is the owner of the NFT he/she can publish a remark RMRK::ROYALTY::${nftId}::${percent}.
Moreover, future owners can also add a royalty fee to the NFT.

Caveats:
We need to make sure that the sum of percents is less or equal to 100.

Why royalty?

A royalty fee is an ongoing payment that franchisees make to franchisors after buying into a franchise.

Royalty is a better name ๐Ÿ‘

Moreover, future owners can also add a royalty fee to the NFT.

I see this as very problematic in different apps consolidating it. Some might drop a single remark like this and all their sales will be rendered invalid. A clumsy team might fall into this trap if they don't test or have plenty of dry runs in a local environment.

I think I prefer the in-JSON royalty value, that way it's bound to the NFT (or collection) and cannot be ignored.

When is NFT minted and the user is the owner of the NFT he/she can publish a remark RMRK::ROYALTY::${nftId}::${percent}.
Moreover, future owners can also add a royalty fee to the NFT.

I don't like the idea of future owners adding a royalty to an NFT that goes to themselves perpetually - it makes sense for the creator or issuer to be sustained perpetually by their past work, that's the purpose of this mechanism, to help artists get what has traditionally gone exclusively to early collectors, but a curator should not have perpetual ownership of the revenue of a work they did not create - that's why I've defined it in the proposal as going to subsequent owners, and not just the first N owners of an NFT

Caveats:
We need to make sure that the sum of percents is less or equal to 100.

yes, absolutely :D

ed-iv commented

One additional kink that I think would be useful is to differentiate between the original issuer and the intended primary benefactor of the proceeds. The use case here is when an artist is minting a piece with the intention of her royalties going an account other than her own, e.g. to an account set up for a charity.

This could be accomplished by adding an optional beneficiary field that overrides the original issuer address address when present.

Leaving this here just as useful tidbit https://eips.ethereum.org/EIPS/eip-2981