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

[RFC] NFT standard philosophy, requirements, and some implementation … #4887

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions sui_programmability/examples/nft_standard/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "nft_standard"
version = "0.0.1"

[dependencies]
Sui = { local = "../../../crates/sui-framework" }

[addresses]
nft_standard = "0x0"
114 changes: 114 additions & 0 deletions sui_programmability/examples/nft_standard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# NFT Standards, Philosophy, and Requirements

# Basics

Sui’s object-centric data model gives Sui objects many of the features of a basic ERC721-style NFT: an owner, the ability to be transferred, a globally unique identifier.

### Individual NFT’s

An NFT is a Sui object (i.e., a struct value whose declared type has the `key` ability, which in turn means the struct value has a field named `id` holding a globally unique ID). All NFT’s are Sui objects, but not every Sui object is an NFT.
Copy link
Contributor

Choose a reason for hiding this comment

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

all NFTs are Sui objects

but wouldn't it be only NFTs on the Sui network/blockchain that are Sui objects vs all NFTs?

Copy link

Choose a reason for hiding this comment

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

On the Sui network:
Token is Object.
NFTs are Objects.
Make a general trading market for Objects, including token defi and NFT defi.
Object is valuable because Storage Fund supports the bottom price.


### Collections

An NFT collection is a Move struct type with the `key` ability. Every NFT collection has a distinct Move struct type, but not every Move struct type corresponds to an NFT collection.

Choose a reason for hiding this comment

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

  • We like the idea that NFTs of a given NFT Collection have their own type T which is expressed in their Collection type Collection<T> , T being a witness type representing the NFT type
  • Our understanding is that this is useful for human readability as well as to facilitate clients distinguish which collection a given NFT belongs to. Do we miss any other benefits?
  • The result of this is that each NFT creator will have to deploy its own module. This is fine and can be abstracted by an SDK, nevertheless these modules should be as light as possible (if we assume 1KB per deployed module 50,000 collections would roughly equate to 50 MB, that’s fine)
  • We believe this module deployed by the NFT creators should serve solely as a type exporter and should not contain any custom logic. Instead, the custom logic would be offloaded to another layer of modules that do not need to be deployed every time there is a new collection

Looking into the example implementation we see the following types:

Collection: Collection<SuimarineNft>
NFT: SuimarineNft

Where SuimarineNft would be the object type NFTs of a given NFT Collection. We had in mind the following:

Collection: Collection<SuimarineNft>
NFT: Nft<SuimarineNft>

This would allow us to leverage the base layer module and its type Nft as a type unifier that can be used across the full spectrum of liquidity layer modules. Moreover, we are currently in the process of merging a newly improved design of our nft-protocol and believe this Single Witness Pattern could be implemented on top of its base contracts. In this new design there is a base module NFT and we implement custom NFT types on top of it (i.e. Unique NFTs, Collectibles, Composable NFTs, Tickets, etc.). We could therefore have the following three layered approach:

BaseLayerNftCustomLayerNftSingleWitness

If we wanted to build a collection of NFT collectibles we could have:
NftCollectiblesSuimarine

Or if we wanted it to be a unique nft collection instead:
NftUniqueNftSuimarine


Collection metadata is a singleton object of type `sui::collection::Collection<T>` created via the [module initializer](https://examples.sui.io/basics/init-function.html) of the module that defines the NFT type `T`. Initializing a collection gives the creator a `MintCap<T>` granting permission to mint NFT's of the given type, and a `RoyaltyCap<T>` granting permission to mint `RoyaltyReceipt<T>`'s (which will be required upon sale--more on this below).
Copy link
Contributor

Choose a reason for hiding this comment

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

might be more clear to use that grants vs granting permission to

and (which will be required)
which is required

upon sale = to sell the collection?

Choose a reason for hiding this comment

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

  • We have given much thought to this. Our current perspective is that if we force all collections to have a Limited Supply via MintCap<T> we could risk leaving out some domain-specific use cases.

  • One use case could be a gaming item that can be minted on demand by the Gaming Creators. If the game wants unlimited supply and does not care about keeping track of the amount of objects minted in the collection object, we can take the most advantage out of parallelisation because we don’t require each mint transaction to mutate the supply in the Collection/MintCap object.

Copy link
Collaborator Author

@sblackshear sblackshear Oct 6, 2022

Choose a reason for hiding this comment

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

  • Definitely agree with the desire to allow unlimited supply as an option.
  • The convention I had in mind for unlimited supply is to set the collection supply to U64_MAX. No one can feasibly mint that many NFT's, and client logic can be taught to render U64_MAX as "unlimited".
  • The prototype code does not require touching the Collection object to mint, for exactly the reasons you mention (want to max out parallelization). I don't think the requirement to touch a MintCap to mint is a barrier to parallelization--if you don't want a single MintCap to be a point of contention, you can always split it into as many sub MintCap's as desired.


### Transfers

Every Sui object whose declared type has the `store` ability can be freely transferred via the polymorphic `transfer::transfer` API in the Sui Framework, or the special `TransferObject` transaction type. Objects without the `store` ability may be freely transferrable, transferrable with restrictions, or non-transferrable—the implementer of the module declaring the type can decide.
Copy link
Contributor

Choose a reason for hiding this comment

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

Sui objects with the store ability (from the declared type) are transferrable using the polymorphic transfer::transfer API in the Sui Framework, or the special TransferObject transaction type. Objects without the store ability can be transferrable, transferrable with restrictions, or non-transferrable as determined by the module implementer that declares the type.

By freely I think you mean "without restriction" but in the context of blockchain some might read it as "without fees"


Beyond the basics, we broadly view NFT standards in two separate parts: display and commerce.
Copy link
Contributor

Choose a reason for hiding this comment

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

I might make this Display and Commerce so it is easier to see the distinction in the following section.


- Display standards give programmers the power to tell clients (wallets, explorers, marketplaces, …) how to display and organize NFT’s. Clients have a “default” scheme for displaying Sui objects (show a JSON representation of all field values); these standards allow programmers to make this representation more visually appealing and end-user friendly.
Copy link
Contributor

Choose a reason for hiding this comment

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

NFTs for plural

Copy link
Contributor

Choose a reason for hiding this comment

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

Consider not using a list for the top level here:
Display standards give programmers ...

  • Clients are the only

Commerce standards ...

  • The intended

These lists lack parallel construction
Client and programmers are the intended audience

Or use
Intended audience includes only clients

- Clients are the only audience of these standards. They are not intended to be used by Sui smart contracts that work with NFT’s
Copy link
Contributor

Choose a reason for hiding this comment

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

NFTs

- Commerce standards allow Sui Move programmers to use libraries for NFT pricing, marketplace listing, royalties, etc. These standards are designed to be compositional building blocks for key NFT-related functionality that can be used either in isolation, or together
Copy link
Contributor

Choose a reason for hiding this comment

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

missing end period

- The intended audience for these standards is both clients and Sui Move programmers.
- Clients are aware of the key functions and events in these libraries. They know how to craft transactions using these functions, use read functions for (e.g.) checking ownership of an NFT, and interpret events emitted by the library to inform the user of on-chain happenings.
- Sui Move programmers use these libraries both for creating NFT’s (e.g., designing an NFT drop using an auction library) and writing NFT infrastructure that build on top of the core libraries (e.g., creating a new royalty policy that plugs into the extensible libraries)
Copy link
Contributor

Choose a reason for hiding this comment

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

missing end period


# Commerce

These standards do not yet exist, but here are:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
These standards do not yet exist, but here are:
These standards do not yet exist, but we include the following:


- the requirements we have in mind
- the philosophy behind these requirements
- some implementation ideas

### Requirements

- **Simple proof of ownership for clients:** We must support checking NFT ownership in a uniform way.
Copy link
Contributor

Choose a reason for hiding this comment

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

you use both a : and a . after the bold term, colon is bold but dot is not

I'd make the first level bullets sections instead of list items and then use a list under that

- This mechanism is intended for off-chain ownership checking: e.g., for a wallet to show a user which NFT’s they own, allowing access to tokengated content, etc. On-chain ownership checking will likely use a separate mechanism that is more direct (e.g., “function `f` checks ownership of a `T` by asking for a parameter of type `&T`).
- If all NFT’s were single-owner objects, this would be easy (just look at the object ownership metadata!), but this is too restrictive—many NFT’s will need to be shared or quasi-shared (e.g., an NFT listed for sale on a marketplace will be quasi-shared, but will still have an owner).
- This should be simple and broadly accessible: either a single function call, or small number of direct object reads encapsulated behind a single API call. This should *not* require an indexer or special API’s not supported by an ordinary full node
- **Listing without lockup**. An ****NFT listed for sale (e.g., on a marketplace) must retain most (but not all) of its functionality
Copy link
Contributor

Choose a reason for hiding this comment

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

****NFT is intentional?

- NFT’s that are listed for sale should still have an owner, and should be usable for tokengating, games, etc.
Copy link
Contributor

Choose a reason for hiding this comment

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

NFTs

- However, it is important that listed NFT’s cannot be mutated by the owner (or if it can, this fact + the risks should be very clear to the buyer)—e.g., the owner of a `Hero` listed for sale at a high price should not be able to remove items from the `Hero`'s inventory just before a sale happens.
Copy link
Contributor

Choose a reason for hiding this comment

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

Last one I will comment on-NFTs

- An NFT should be able to be listed on multiple marketplaces at the same time. Note that the natural way of implementing marketplace listing (make the NFT object a child of a shared object marketplace) does not support this.
- **Low-level royalty enforcement**. We believe royalties are the raison d'être of NFT’s from the creator perspective—this is a feature physical or web2 digital creations cannot easily have.
Copy link
Contributor

@porkbrain porkbrain Oct 5, 2022

Choose a reason for hiding this comment

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

Perhaps a useful angle to divide this problem under is to consider the following scenarios separately:

  1. Both client and marketplace want to pay royalties
  2. Marketplace wants to avoid royalties but the client does not
  3. Client wants to avoid royalties but the marketplace does not
  4. Both client and marketplace are disingenuous and want to avoid royalties

Case 4. is fundamentally lost, there will always exist a way for them to settle trades.

Case 1. is an ideal world scenario where we wouldn't have to build anything to perform royalty enforcement.

Case 3. is simple to solve if we let the marketplace tell the Safe: "this is how much has been paid for the NFT, now go and do your thing with the appropriate FooNft::pay_royalty function." We implemented this today in the OB to see how it would work.

Case 4. is harder to enforce. If most popular wallets want to enforce royalties, what mechanism can we provide them to validate that the marketplace has indeed paid the expected amount, otherwise fail the tx?
If we use the approach of RoyaltyReceipt then we must somehow in the marketplace validate that this is indeed the expected amount of royalty paid, otherwise scenario 2. is not covered. Additionally, a client might not always know the exact price they will pay upfront (e.g. contracts where slippage is appropriate such as order book.)

- Ironclad royalty enforcement is impossible (e.g., can give away an NFT for free and do a side payment). But we want to make it highly inconvenient (e.g., you’d have to go outside the accepted standards) and socially unacceptable (e.g., you will be shunned by users, creators, even other marketplaces) to bypass the standards
- We want to enforce royalties on *sales*, not transfers. A user should be able to transfer NFT’s between their own addresses, or give an NFT to another user.
- **Low-level commission enforcement for sale facilitator**. Same as above, but for the party (e.g., marketplace, wallet, explorer) that facilitates the sale. We need to give both creators (via royalties) and facilitators (via commissions) a business model.
- **Borrowing**. Another distinguishing feature of NFT’s compared to physical or web2 digital assets is the ability to implementing trusted or conditional borrowing .
Copy link
Contributor

Choose a reason for hiding this comment

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

ability to implement

borrowing.

- Allowing immutable borrowing of assets is straightforward
- Allowing mutable borrowing of assets is possible, but should be done with care (e.g., the borrower of a `Hero` probably needs to be mutable so using the `Hero` in a game lets the `Hero` level up, but we don’t want to allow the borrower to sell off all of the `Hero`'s items)
- **Arbitrary customization of royalties, commission, minting/sale method (e.g., kind of auction used).** The mechanisms for each of these is rapidly evolving, and we do not think a standard that enforces a fixed menu of choices will age well.
Copy link
Contributor

Choose a reason for hiding this comment

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

. is bold in this one but not others

- Here, “arbitrary” means that a creator or marketplace can implement a policy in arbitrary Move code with no restrictions (e.g., the policy might involve touching 10 shared objects, and that is ok).
- The recommended technical mechanism for this sort of extensibility is the “receipt” pattern; e.g., a `buy<T>(royalty: RoyaltyReceipt<T>)`, where the creator of the `T` collection gets to write arbitrary code that decides when a `RoyaltyReceipt<T>` can be created.
Copy link
Contributor

Choose a reason for hiding this comment

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

recommended?

- We should have safe, convenient libraries for common royalty policies (e.g., no royalty, royalty split among set of addresses), commissions, and auctions
- **Price discovery aka “NFT orderbook”**. The standards for listing NFT’s should provide (via event queries or other mechanisms) a common framework for discovering the price for a given NFT, the floor price of a collection, etc.

### Implementation thoughts

One approach to satisfying a number of these requirements is to define a shared `NFTSafe` that is associated with a specific address (similar to the `Safe` for coins proposed here: ‣).

- The owner of the `NFTSafe` has an `OwnerCap` that allows them to mint `TransferCap`'s, which give the holder permission to transfer the NFT between two different `NFTSafe`'s.
Copy link
Contributor

Choose a reason for hiding this comment

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

Transfercaps tho it looks weird like that. Do these really need to be in code format since you're referring to the concept of NFT Safe vs a code element in an API?

NFTSafes
tho it may be easier to read as "to transfer from one NFTSafe to another"

- When an owner lists an NFT on a marketplace, they would actually list the `TransferCap`--the NFT continues to sit in the safe.
- A `TransferCap` doesn’t give the holder unconditional permission to transfer the NFT—they must also satisfy the royalty and commission policies. This is how royalty enforcement happens.
- If you have an `OwnerCap`, you can take an NFT out of the safe (i.e., make it a single-owner object), or transfer it to a different safe without paying royalties or commissions. This satisfies the “let a user transfer NFT between addresses” requirements
- Borrowing an NFT can be implemented through a `BorrowCap` that is similar to `TransferCap`, but only allows the holder to temporarily take the NFT out of a safe during a transaction—they must return it before the transaction ends
Copy link
Contributor

Choose a reason for hiding this comment

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

To implement NFT borrowing, a BorrowCap that

but allows the holder to only temporarily

- The “hot potato” design pattern is the natural implementation approach for this
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally use or don't use ending periods for each list entry

- An `OwnerCap` also allows the owner to
- transfer all of their NFT’s to a different user (by transferring the `OwnerCap`),
- gate access to NFT’s via a multisig (by putting the `OwnerCap` in a multisig smart contract wallet)
- Use [KELP](https://eprint.iacr.org/2021/289.pdf)-like social recovery schemes to protect the user against key loss (by putting the `OwnerCap` in a social recovery smart contract wallet)
- NFT minting can either transfer the freshly created NFT directly to a user address, or put it into the user’s `NFTSafe`. Some users might want to allow arbitrary deposits of NFT’s into their safe, whereas others might only want to give themselves permission to pull an NFT into the safe (e.g., as a means of curation or spam prevention)
- A single, heterogenous `NFTSafe` is probably the simplest/most convenient, but one could also imagine `NFTSafe<T>` that partitions by type. This draft PR goes for the latter.
- A reasonable implementation of the `NFTSafe` idea will rely heavily on the forthcoming “dynamic child object access” feature: https://github.com/MystenLabs/sui/issues/4203.

# Display

Choose a reason for hiding this comment

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

We were wondering what the vision is when it comes to wallets organizing NFTs by their business-level domains (i.e. Art, Profile Pictures, Gaming Assets, etc.).

The way we are currently approaching this is by having a tags field in the collection object where collection can push tags such as art, pfp with the idea of having wallets reading these tags and displaying them in the associated NFT tab. That way a an NFT collection can tag itself as a art and simultaneously as a gaming_asset collection if it fits both purposes.

Any thoughts on this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, I think this makes a lot of sense!

Copy link
Contributor

Choose a reason for hiding this comment

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

For display standards, we discussed the idea of creating some trait-like behaviour. Relevant for the Sui NS project was the expiry of an NFT.

If an object has been recognized as some NFT, wallets can apply an Expires trait optionally. They do this by looking for metadata.expiry date (unix or ISO 8601?) or - if missing - then metadata.is_expired bool flag.
Any NFT can have expiry logic. By having this as a trait rather than on e.g. only domain name NFTs, we have a more composable standard.


The display standard is organized as:

- A set of (field name, type) pairs with a special interpretation on the client side. When a non-wrapped Sui object has a field with the given name and type, these rules will be applied.
Copy link
Contributor

Choose a reason for hiding this comment

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

rules are applied
or these rules apply to avoid passive voice


| Field Name | Move type | Description |
| --- | --- | --- |
| name | std::string::String, std::ascii::String | Name of the NFT. Shown at the top of the NFT view |
| description | std::string::String, std::ascii::String | Description of the NFT. Shown in the NFT properties view. |
| url | sui::url::Url, sui::url::UrlCommitment, vector<sui::url::Url>, vector<sui::url::UrlCommitmen>t> | URL containing the content for an NFT, or a vector containing several such URLs. If the URL contains an image, it will be displayed on the left of the NFT view |
Copy link
Contributor

Choose a reason for hiding this comment

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

image, it displays on the left or
is displayed

- A set of types with special display interpretation on the client side. When *any* Sui object (wrapped or non-wrapped) has a field of the given type that does not match one of the rules in the previous table, these rules will be applied.
Copy link
Contributor

Choose a reason for hiding this comment

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

rules apply


| Move type | Description |
| --- | --- |
| std::string::String, std::ascii::String | Displayed as a UTF8-encoded string for std::string::String or a vector<u8> if the underlying bytes are not valid ASCII., and an ASCII-encoded string for std::ascii::String. Displayed as a vector<u8> |
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the period used in this one? ASCII., ?

| sui::url::Url, sui::url::UrlCommitment | Displayed as a clickable hyperlink |
| sui::object::ID,
sui::object::UID | Displayed in hex with a leading 0x (e.g., 0xabc..), with a clickable hyperlink to the object view page in the Sui explorer |
| std::option::Option<T> | Displayed as None if the Option does not contain a value, and Some(_) with display rules applied to the contents if the Option contains a value. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Option does not (extra space)

Why is it not Option here?


## Philosophy

The aim of this design is to gives Sui programmers maximum flexibility—they don’t have to use special wrapper types (e.g., `NFT<T>`) to implement NFT’s with visual elements. They can simply define ordinary Sui objects that use the special field and type names, and clients will understand them.
Copy link
Contributor

Choose a reason for hiding this comment

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

to give


## Status

- The standards above are supported by several (perhaps all?) Sui wallets and the explorer
Copy link
Contributor

Choose a reason for hiding this comment

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

Sui Explorer?

- The standards above are fairly bare-bones, are we are very open to tweaks and extensions as long as they preserve the core philosophy of the display standards. For example:
- other media types
- distinguishing between different kinds of URL’s (e.g. an image URL vs the URL for the website of a collection)
- a distinguished type for collection metadata (as opposed to NFT metadata)
- a distinguished field/type to identify the collection creator(s)
- storing media directly in an NFT (rather than at a URL)
- finalizing the standards for `UrlCommitment`
- There should be a way to “opt out” of being shown as an NFT for an object that happens to define some or all of the special (name, field) pairs
87 changes: 87 additions & 0 deletions sui_programmability/examples/nft_standard/sources/collection.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module nft_standard::collection {
struct Collection<phantom T, M: store> has key, store {
id: UID,
// Standard fields that every collection needs go here.
// Pulling a few obvious ones from https://github.com/Origin-Byte/nft-protocol/blob/main/sources/collection/collection.move and
// https://github.com/suiet/standard/blob/main/docs/nft/collections.md
// but more will be needed
/// Address that created this collection
creator: address,
/// Name of the collection. TODO: should this just be T.name?
name: String,
/// Description of the collection
description: String,
/// The maximum number of instantiated NFT objects. Use U64_MAX if there is no max
total_supply: u64,
// ... more standard fields
/// Custom metadata outside of the standard fields goes here
custom_metadata: M,
}

/// Proof that the given NFT is one of the limited `total_supply` NFT's in `Collection`
struct CollectionProof has store {
collection_id: ID
Copy link
Contributor

@damirka damirka Sep 30, 2022

Choose a reason for hiding this comment

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

Would be cool to have an identifier in collection as well (eg 50/100). Some people like myself prefer to hunt for magic numbers in edition.

}

/// Grants the permission to mint `num_remaining` NFT's of type `T`
/// The sum of `num_remaining` for all mint caps + the number of instantiated
/// NFT's should be equal to `total_supply`.
/// This is a fungible type to support parallel minting, giving a buyer the permission
/// mint themselves, allowing multiple parties to mint, and so on.
struct MintCap<phantom T> has key, store {
id: UID,
/// ID of the collection that this MintCap corresponds to
collection: ID,
/// Number of NFT's this cap can mint
num_remaining: u64,
}

/// Grants the permission to mint `RoyaltyReceipt`'s for `T`.
/// Receipts are required when paying for NFT's
struct RoyaltyCap<phantom T> has key, store {
Copy link
Contributor

@porkbrain porkbrain Oct 2, 2022

Choose a reason for hiding this comment

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

Looking at this with a fresh perspective: perhaps what we could do is to have a method in T which can create a new RoyaltyCap at will by the admin. Then, the admin - after they verified that a trading facilitator contract adheres to royalties - can give this object to that contract.

This means RoyaltyCap effectively acts as a whitelist.

Copy link
Contributor

Choose a reason for hiding this comment

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

Therefore one could either go via the T::buy_nft royalty collection fn or be trusted by the collection admin by getting RoyaltyCap from them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this is an interesting idea. The RoyaltyReceipt pattern is my least favorite part of this current proposal--I think the current setup creates too much friction for flexibility that is likely not needed. Some iteration on other techniques for low-level royalty enforcement would definitely be useful.

id: UID,
collection: ID,
}

/// Proof that the royalty policy for collection `T` has been satisfied.
/// Needed to complete a sale of an NFT from a Collection<T>`
struct RoyaltyReceipt<phantom T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. The mechanism for collecting royalties with RoyaltyReceipt type: we believe that this should be left up to the TSCs (trading smart contracts.)

2a. The foo_nft::buy function is fool-able in the sense that a TSC could provide the least valuable input possible. I.e. there is no way to trust the marketplace about correct inputs into the function.

2b. Therefore it seems fit to change the purpose of RoyaltyReceipt to "force the trading contracts to consider correct royalty implementation.", rather than making it inconvenient to bypass royalties.

id: UID,
collection: ID,
}

/// Instantiate a collection for T.
/// To be called from the module initializer in the module that declares `T`
public fun create<T, M: store>(
_witness: &T,
name: String,
total_supply: u64,
ctx: &mut TxContext,
): (Collection<T,M>, MintCap<T>, RoyaltyCap<T>) {
abort(0)
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: I would suggest making witness a one-time one. Otherwise it's too easy to cheat on the system.

}

/// To be called from the module that declares `T`, when packing a value of type `T`.
/// The caller should place the `CollectionProof` in a field of `T`.
/// Decreases `num_remaining` by `amount`
public fun mint<T>(mint_cap: &mut MintCap<T>): CollectionProof {
abort(0)
}

/// To be called from the module that declares `T`.
/// The caller is responsible for gating usage of the `royalty_cap` with its
/// desired royalty policy.
public fun create_receipt(royalty_cap: &mut RoyaltyCap<T>): RoyaltyReceipt<T> {
abort(o)
}

/// Split a big `mint_cap` into two smaller ones
public fun split<T>(mint_cap: &mut MintCap<T>, num: u64): MintCap<T> {
abort(0)
}

/// Combine two `MintCap`'s
public fun join<T>(mint_cap: &mut MintCap<T>, to_join: MintCap<T>) {
abort(0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module nft_standard::example_marketplace {

/// A shared object holding NFT listings
struct Marketplace {
/// NFT's listed for sale in this marketplace, indexed by their id's
// note: Table and ObjectTable will be added by https://github.com/MystenLabs/sui/issues/4203
listings: Table<ID, Listing>,
// commission taken on each filled listing. a flat fee, for simplicity.
commission: u64
}

struct Listing has store {
/// Price of the item in SUI
price: u64,
/// Capability to pull the item out of the appropriate `Safe` when making a sale
transfer_cap: TransferCap
}

fun init() {
// share the marketplace object, set the initial commission
}

public fun list(transfer_cap: TransferCap, marketplace: &mut Marketplace) {
// create a listing from transfer_cap add it to marketplace
}

public fun buy<T>(
royalty: RoyaltyReceipt<T>, coin: &mut Coin<SUI>, id: ID, safe: &mut Safe<T>, marketplace: &mut Marketplace
Copy link
Contributor

Choose a reason for hiding this comment

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

I reckon the trading contracts should be the ones enforcing royalties, otherwise, as a client, I would write my own interaction with this method in which I fool the buy_nft fn to get a RoyaltyReceipt for cheap.

In other cases, I might not know the price I will pay upfront. If royalty is a percentage cut, and I buy something from an orderbook, the state can change since I sent the request.

): T {
// ...extract marketplace.commission from coin
// ... extract the Listing for ID, get the TransferCap out of it
safe::buy_nft(transfer_cap, royalty, id, safe);
}
}
Loading