NFT Metadata Views on Flow
MetadataViews
on Flow offer a standardized way to represent onchain metadata
across different NFTs. Through its integration, developers can ensure
that different platforms and marketplaces can interpret the NFT metadata
in a unified manner. This means that when users visit different websites,
wallets, and marketplaces,
the NFT metadata will be presented in a consistent manner,
ensuring a uniform experience across various platforms.
It is important to understand this document so you can make meaningful decisions about how to manage your project's metadata as support for metadata views does not happen by default. Each project has unique metadata and therefore will have to define how they expose it in unique ways.
A view is a standard Cadence struct that represents a specific type of metadata, such as a Royalty specification:
_10access(all) struct Royalty {_10 /// Where royalties should be paid to_10 access(all) let receiver: Capability<&{FungibleToken.Receiver}>_10_10 /// The cut of the sale that should be taken for royalties._10 access(all) let cut: UFix64_10_10 /// Optional description of the royalty_10 access(all) let description: String_10}
or a rarity description:
_10access(all) struct Rarity {_10 /// The score of the rarity as a number_10 access(all) let score: UFix64?_10_10 /// The maximum value of score_10 access(all) let max: UFix64?_10_10 /// The description of the rarity as a string._10 access(all) let description: String?_10}
This guide acts as a specification for the correct ways to use each metadata view. Many of the standard metadata views do not have built-in requirements for how they are meant to be used, so it is important for developers to understand the content of this document so third party apps can integrate with their smart contracts as easily and effectively as possible.
If you'd like to follow along while we discuss the concepts below, you can do so by referring to the ExampleNFT contract. Additionally, here is the source code for the
ViewResolver
contract and theMetadataViews
contract.
Flowty has also provided a useful guide for how to manage metadata views properly in order to be compatible with their marketplace. This guide is very useful because all of their advice is generally good advice for any NFT contract, regardless of what marketplace it is using.
Two Levels of Metadata: An Overview
Metadata in Cadence is structured at two distinct levels:
-
Contract-Level Metadata: This provides an overarching description of the entire NFT collection/project. Any metadata about individual NFTs is not included here.
-
NFT-Level Metadata: Diving deeper, this metadata relates to individual NFTs. It provides context, describes rarity, and highlights other distinctive attributes that distinguish one NFT from another within the same collection.
While these distinct levels describe different aspects of a project, they both use the same view system for representing the metadata and the same basic function calls to query the information, just from different places.
Understanding ViewResolver
and MetadataViews.Resolver
When considering Flow and how it handles metadata for NFTs,
it is crucial to understand two essential interfaces:
ViewResolver
and MetadataViews.Resolver
.
Interfaces
serve as blueprints for types that specify the required fields and methods
that your contract or composite type must adhere to
to be considered a subtype of that interface.
This guarantees that any contract asserting adherence to these interfaces
will possess a consistent set of functionalities
that other applications or contracts can rely on.
ViewResolver
for Contract-Level Metadata:- This interface ensures that contracts, particularly those encapsulating NFT collections, conform to the Metadata Views standard.
- Through the adoption of this interface, contracts can provide dynamic metadata that represents the entirety of the collection.
MetadataViews.Resolver
(ViewResolver.Resolver
in Cadence 1.0) for NFT-Level Metadata:- Used within individual NFT resources, this interface ensures each token adheres to the Metadata standard format.
- It focuses on the distinct attributes of an individual NFT, such as its unique ID, name, description, and other defining characteristics.
Core Functions
Both the ViewResolver
and MetadataViews.Resolver
utilize the following core functions:
getViews
Function
This function provides a list of supported metadata view types,
which can be applied either by the contract (in the case of ViewResolver
)
or by an individual NFT (in the case of MetadataViews.Resolver
).
_10access(all) fun getViews(): [Type] {_10 return [_10 Type<MetadataViews.Display>(),_10 Type<MetadataViews.Royalties>(),_10 ..._10 ]_10}
resolveView
Function
Whether utilized at the contract or NFT level, this function's role is to deliver the actual metadata associated with a given view type.
The caller provides the type of the view they want to query as the only argument,
and the view is returned if it exists, and nil
is returned if it doesn't.
_10access(all) fun resolveView(_ view: Type): AnyStruct? {_10 switch view {_10 case Type<MetadataViews.Display>():_10 ..._10 ..._10 }_10 return nil_10}
As you can see, the return values of getViews()
can be used as arguments
for resolveView()
if you want to just iterate through all the views
that an NFT implements.
NFT-Level Metadata Implementation
NFT-level metadata addresses the unique attributes of individual tokens within a collection. It provides structured information for each NFT, including its identifier, descriptive elements, royalties, and other associated metadata. Incorporating this level of detail ensures consistency and standardization among individual NFTs, making them interoperable and recognizable across various platforms and marketplaces.
Core Properties
In the code below, an NFT has properties such as
its unique ID, name, description, and others.
When we add the NonFungibleToken.NFT
and by extension,
the MetadataViews.Resolver
to our NFT resource,
we are indicating that these variables will adhere to the specifications
outlined in the MetadataViews contract for each of these properties.
This facilitates interoperability within the Flow ecosystem
and assures that the metadata of our NFT can be consistently accessed
and understood by various platforms and services that interact with NFTs.
_10access(all) resource NFT: NonFungibleToken.NFT {_10 access(all) let id: UInt64_10 access(all) let name: String_10 access(all) let description: String_10 access(all) let thumbnail: String_10 access(self) let royalties: [MetadataViews.Royalty]_10 access(self) let metadata: {String: AnyStruct}_10 ..._10}
To make this possible though, it is vital that projects all use the standard metadata views in the same way, so third-party applications can consume them in standard ways.
For example, many metadata views have String
-typed fields. It is difficult
to enforce that these fields are formatted in the correct way, so it is important
for projects to be dilligent about how they use them. Take Traits
for example,
a commonly misused metadata view:
_10access(all) struct Trait {_10 // The name of the trait. Like Background, Eyes, Hair, etc._10 access(all) let name: String_10 ..._10 ..._10}
The name of the trait should be formatted in a way so that it is easy to display on a user-facing website. Many projects will use something like CamelCase for the value, so it looks like "HairColor", which is not pretty on a website. The correct format for this example would be "Hair Color". This is just one of many common view uses that projects need to be aware of to maximize the chance of success for their project.
Metadata Views for NFTs
MetadataViews
types define how the NFT presents its data.
When invoked, the system knows precisely which view to return,
ensuring that the relevant information is presented consistently across various platforms.
In this section of the document, we will explore each metadata view and describe
how projects should properly use them.
Display
This view provides the bare minimum information about the NFT
suitable for listing or display purposes. When the Display
type is invoked,
it dynamically assembles the visual and descriptive information
that is typically needed for showcasing the NFT in marketplaces or collections.
_10case Type<MetadataViews.Display>():_10 return MetadataViews.Display(_10 name: self.name,_10 description: self.description,_10 thumbnail: MetadataViews.HTTPFile(_10 url: self.thumbnail_10 )_10 )
If the thumbnail is a HTTP resource:
_10thumbnail : MetadataViews.HTTPFile(url: *Please put your url here)
If the thumbnail is an IPFS resource:
_10//_10thumbnail : MetadataViews.IPFSFile(_10 cid: thumbnail cid, // Type <String>_10 path: ipfs path // Type <String?> specify path if the cid is a folder hash, otherwise use nil here_10)
Note about SVG files on-chain: SVG field should be sent as thumbnailURL
,
should be base64 encoded, and should have a dataURI prefix, like so:
_10data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InJlZCIvPjwvc3ZnPg==
Editions
The Editions
view provides intricate details regarding the particular release of an NFT
within a set of NFTs with the same metadata.
This can include information about the number of copies in an edition,
the specific NFT's sequence number within that edition, or its inclusion in a limited series.
When the Editions
view is queried, it retrieves this data,
providing collectors with the information they need to comprehend
the rarity and exclusivity of the NFT they are interested in.
An NFT can also be part of multiple editions, which is why the Editions
view
can hold any number of Edition
structs in an array.
For example, if an NFT is number 11 of 30 of an exclusive edition,
the code to return the Editions
view would look like this:
_10case Type<MetadataViews.Editions>():_10 let editionInfo = MetadataViews.Edition(_10 name: "Example NFT Edition",_10 number: 11,_10 max: 30_10 )_10 return MetadataViews.Editions([editionInfo])
Serial Number Metadata
The Serial
metadata provides the unique serial number of the NFT,
akin to a serial number on a currency note or a VIN on a car.
This serial number is a fundamental attribute that certifies the individuality
of each NFT and is critical for identification and verification processes.
Serial numbers are expected to be unique among other NFTs from the same project.
Many projects are already using the NFT resource's
[globally unique UUID](resource's globally unique UUID)
as the ID already, so they will typically also use that as the serial number.
_10case Type<MetadataViews.Serial>():_10 return MetadataViews.Serial(self.uuid)
Royalties Metadata
Royalty information is vital for the sustainable economics of the creators in the NFT space.
The Royalties
metadata view
defines the specifics of any royalty agreements in place,
including the percentage of sales revenue that will go to the original creator
or other stakeholders on secondary sales.
Each royalty view contains a fungible token receiver capability where royalties should be paid:
_10access(all) struct Royalty {_10_10 access(all) let receiver: Capability<&{FungibleToken.Receiver}>_10_10 access(all) let cut: UFix64_10}
here is an example of how an NFT might return a Royalties
view:
_13case Type<MetadataViews.Royalties>():_13 // Assuming each 'Royalty' in the 'royalties' array has 'cut' and 'description' fields_13 let royalty =_13 MetadataViews.Royalty(_13 // The beneficiary of the royalty: in this case, the contract account_13 receiver: ExampleNFT.account.capabilities.get<&AnyResource{FungibleToken.Receiver}>(/public/GenericFTReceiver),_13 // The percentage cut of each sale_13 cut: 0.05,_13 // A description of the royalty terms _13 description: "Royalty payment to the original creator"_13 )_13 }_13 return MetadataViews.Royalties(detailedRoyalties)
If someone wants to make a listing for their NFT on a marketplace,
the marketplace can check to see if the royalty receiver
accepts the seller's desired fungible token by calling
the receiver.getSupportedVaultTypes(): {Type: Bool}
function via the receiver
reference:
_10let royaltyReceiverRef = royalty.receiver.borrow()_10 ?? panic("Could not borrow a reference to the receiver")_10let supportedTypes = receiverRef.getSupportedVaultTypes() _10if supportedTypes[**royalty.getType()**] {_10 // The type is supported, so you can deposit_10 recieverRef.deposit(<-royalty)_10} else {_10 // if it is not supported, you can do something else,_10 // like revert, or send the royalty tokens to the seller instead_10}
If the desired type is not supported, the marketplace has a few options.
They could either get the address of the receiver by using the
receiver.owner.address
field and check to see if the account
has a receiver for the desired token, they could perform the sale without a royalty cut,
or they could abort the sale since the token type isn't accepted by the royalty beneficiary.
You can see example implementations of royalties in the ExampleNFT
contract
and the associated transactions and scripts.
NFTs are often sold for a variety of currencies, so the royalty receiver should ideally
be a fungible token switchboard receiver that forwards any received tokens
to the correct vault in the receiving account.