Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIP-0102? | Royalty Datum Metadata #551

Merged
merged 30 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1b8654f
First pass: introduce royalty metadata as specified in Nebula
SamDelaney May 6, 2023
461e207
minor fixes
SamDelaney May 16, 2023
a05fa4a
fix token name
SamDelaney May 18, 2023
860a77a
consolidate TN spec & rename token -> NFT
SamDelaney May 25, 2023
1fb1f14
require royalty NFT policyId to match reference NFT
SamDelaney Jun 5, 2023
f198e2a
Change royalty location to suggestion
SamDelaney Jun 7, 2023
693adac
Elaborate on fee calculation
SamDelaney Jul 13, 2023
1709808
Add 500 token to CIP 67 registry
SamDelaney Jul 13, 2023
6409e4c
move to a new CIP
SamDelaney Aug 9, 2023
304dcb0
reference datum flag spec
SamDelaney Aug 10, 2023
6893503
Fix leftover whitespace
SamDelaney Aug 10, 2023
fce05fc
Path to Active & References
SamDelaney Aug 21, 2023
0b9a955
Merge branch 'cip68-royalties' of https://github.com/ikigai-github/CI…
SamDelaney Aug 21, 2023
34089a1
update CIP-67 registry entry
SamDelaney Aug 21, 2023
357202f
changes per review
SamDelaney Aug 29, 2023
713aad5
Update preamble to match CIP-0001 standard
SamDelaney Aug 30, 2023
0041a70
assigned 102 as CIP number
rphair Sep 5, 2023
c51bc4a
move docs to new folder
SamDelaney Sep 5, 2023
506447d
Merge branch 'cip68-royalties' of https://github.com/ikigai-github/CI…
SamDelaney Sep 5, 2023
7725aaf
update registry entry
SamDelaney Sep 5, 2023
6d897eb
add registry documentation link
SamDelaney Sep 6, 2023
4ecab23
Apply suggestions from code review
SamDelaney Oct 17, 2023
2e7f21f
all header level fixes agreed at CIP meeting
rphair Oct 17, 2023
d77752c
all header level fixes agreed at CIP meeting
rphair Oct 17, 2023
ecf6b28
agreed at meeting null implementors mandatory
rphair Oct 17, 2023
1a177b4
agreed at meeting to use tickboxes
rphair Oct 17, 2023
f28f8c2
agreed at meeting to use tickboxes
rphair Oct 17, 2023
38a3146
agreed at meeting to use tickboxes
rphair Oct 17, 2023
9480534
adding `cddl` syntax tag pending eventual GitHub support
rphair Oct 17, 2023
75eb324
adding `cddl` syntax tag pending eventual GitHub support
rphair Oct 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CIP-0067/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@
"class": "RFT",
"description": "CIP-0068 - Datum Metadata Standard (444 sub standard)"
},
{
"asset_name_label": 500,
"class": "NFT",
"description": "CIP-0068 - Datum Metadata Standard (500 sub standard)"
}
]
88 changes: 87 additions & 1 deletion CIP-0068/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ datum = #6.121([metadata, version, extra])

#### 222 NFT Standard

Besides the necessary standard for the `reference NFT` we're introducing three specific token standards in this CIP. Note that the possibilities are endless here and more standards can be built on top of this CIP for FTs, other NFTs, rich fungible tokens, etc. The first is the `222` NFT standard with the registered `asset_name_label` prefix value
Besides the necessary standard for the `reference NFT` we're introducing four specific token standards in this CIP. Note that the possibilities are endless here and more standards can be built on top of this CIP for FTs, other NFTs, rich fungible tokens, etc. The first is the `222` NFT standard with the registered `asset_name_label` prefix value

| asset_name_label | class | description |
| --------------------------- | ------------ | -------------------------------------------------------------------- |
Expand Down Expand Up @@ -329,6 +329,92 @@ We want to bring the metadata of the RFT `d5e6bf0500378d4f0da4e8dde6becec7621cd8
3. Reference the output in the transaction. (off-chain)
4. Verify validity of datum of the referenced output by checking if policy ID of `reference NFT` and `user token` and their asset names without the `asset_name_label` prefix match. (on-chain)

#### 500 Royalty Standard

The fourth introduced standard is the `500` Royalty NFT standard with the registered `asset_name_label` prefix value

| asset_name_label | class | description |
| --------------------------- | ------------ | -------------------------------------------------------------------- |
| 500 | NFT | Royalty NFT stored in a UTxO containing a datum with royalty information |

##### Class

The `royalty NFT` is an NFT (non-fungible token).

##### Pattern

The `royalty NFT` **must** have an identical `policy id` as the collection.

The `asset name` **must** be `001f4d70526f79616c7479` (hex encoded), it contains the [CIP-0067](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0067/README.md) label `500` followed by the word "Royalty".

Example:\
`royalty NFT`: `(500)Royalty`\
`reference NFT`: `(100)Test123`

##### Metadata

The royalty info datum is specified as follows (CDDL):

```cddl
big_int = int / big_uint / big_nint
big_uint = #6.2(bounded_bytes)
big_nint = #6.3(bounded_bytes)

optional_big_int = #6.121([big_int]) / #6.122([])

royalty_recipient = #6.121([
address, ; definition can be derived from:
; https://github.com/input-output-hk/plutus/blob/master/plutus-ledger-api/src/PlutusLedgerApi/V1/Address.hs#L31
int, ; variable fee ( calculation: ⌊1 / (fee / 10)⌋ ); integer division with precision 10
optional_big_int, ; min fee (absolute value in lovelace)
optional_big_int, ; max fee (absolute value in lovelace)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the min and max fee optional or are they required? If so, should there be a note that if you do not wish to set them then these values should be set to 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are optional. optional_big_int is defined in CDDL above this, but perhaps it should be expressed in a less niche format as well.

They are both expected to be a bigint or not be included. Max fee being set to 0 to represent no max fee would be confusing, as it could easily be misread to mean no fees at all, i.e. if the implementer of the protocol missed that wrinkle.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Derp yeah, I guess I didn't read the names of the variables very well, but I would still add the ? to indicate in the CDDL that they are optional :)

])

royalty_recipients = [ * royalty_recipient ]

; version is of type int, we start with version 1
version = 1

; Custom user defined plutus data.
; Setting data is optional, but the field is required
; and needs to be at least Unit/Void: #6.121([])
extra = plutus_data

royalty_info = #6.121([royalty_recipients, version, extra])
```

Example of onchain variable fee calculation:
```
; Given a royalty fee of 1.6% (0.016)

; To store this in the royalty datum
1 / (0.016 / 10) => 625

; To read it back
10 / 625 => 0.016
```
Because the computational complexity of Plutus primitives scales with size, this approach significantly minimizes resource consumption.

To prevent abuse, it is **recommended** that the `royalty NFT` is stored at the script address of a validator that ensures the specified fees are not arbitrarily changed, such as an always-fails validator.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should change the conception that we had during the writing of CIP-27 that royalties should never change. What if I want to put royalties on a collection but only for the first year, or to remove royalties if I no longer intend to continue supporting and working on a project? Having mutable royalties does introduce additional complexity in needing to maintain the token and/or lock it in a script that only you (or a DAO multisig, etc) can control... But probably best for the long term if we do not encourage people to lock up minUTxO worth of Lovelace in a token just for the sake of "not changing the royalties".

Copy link
Contributor Author

@SamDelaney SamDelaney Jul 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this notion to some degree. There are lots of perfectly valid use cases for changing royalties. The key word here is recommended. With this distinction we allow for such use cases, as well as accommodating existing protocols such as Nebula which store the royalty in wallets rather than script addresses.

However, I believe the recommendation against arbitrary royalty control is important. A collection whose fees are controlled arbitrarily can be easily sabotaged at the whim of the creator by increasing the royalties beyond a reasonable amount. This is bad assurance for buyers, and we should set the precedent for better schemes - the simplest of which is an always-fails validator.


##### Retrieve metadata as 3rd party

A third party has the following NFT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(222)TestToken` and they want to lookup the royalties. The steps are

1. Construct `royalty NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(500)Royalty`
2. Look up `royalty NFT` and find the output it's locked in.
3. Get the datum from the output and lookup metadata by going into the first field of constructor 0.
4. Convert to JSON and encode all string entries to UTF-8 if possible, otherwise leave them in hex.

##### Retrieve metadata from a Plutus validator

We want to bring the royalty metadata of the NFT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(222)TestToken` in the Plutus validator context. To do this we

1. Construct `royalty NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(500)Royalty` (off-chain)
2. Look up `royalty NFT` and find the output it's locked in. (off-chain)
3. Reference the output in the transaction. (off-chain)
4. Verify validity of datum of the referenced output by checking if policy ID of `royalty NFT` and `user token` and their asset names without the `asset_name_label` prefix match. (on-chain)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having you tested the realistic feasibility of adding this logic to a standard "marketplace" smart contract? Especially with multiple royalty recipients? While this is certainly a novel concept and a step in the right direction, I wonder on the impact to CPU, memory, and subsequent transaction fees by using this reference token datum versus other, largely off-chain solutions atm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have implemented this in Grabbit, but haven't benchmarked it very thoroughly yet. I can get back to you on CPU, memory, etc. on the client side.

As far as TX fees go, this shouldn't have much of an impact. Reading the fee is as simple as parsing the datum, which doesn't require consuming it at all.

One valid concern is the memory cost to the blockchain itself. This is definitely something worth thinking about, and the design has carefully considered memory usage to be as minimal as possible. I personally think the ability for validators to read the royalties directly & trustlessly is worth the cost many times over.

## Rationale

Without seperation of `reference NFT` and `user token` you lose all flexibility and moving the `user token` would be quite cumbersome as you would need to add the metadata everytime to the new output where the `user token` is sent to. Hence you separate metadata and `user token` and lock the metadata inside another UTxO, so you can freely move the `user token` around.
Expand Down