🔧 Upgrade to Cadence 1.0 🔧
The highly anticipated Crescendo network upgrade is coming soon with 20+ new Cadence 1.0 features and EVM equivalence.
Simple NFT Viewer
This example project will show you how to build a simple viewer that will allow you to view NFTs that conform to the NFT and MetadataViews standards.
This tutorial will mostly ignore the C# code that actually displays the NFTs and focus on a high level summary of the steps used.
When querying the blockchain we utilize four scripts:
* [GetCollections.cdc](Cadence/GetCollections.cdc) - Gets a list of Collections that conform to NFT.Collection for a given address
* [GetNftIdsForCollection.cdc](Cadence/GetNftIdsForCollection.cdc) - Gets a list of all NFT IDs that are contained in a given collection
* [GetDisplayDataForIDs.cdc](Cadence/GetDisplayDataForIDs.cdc) - Gets just the display data for a given NFT
* [GetFullDataForID.cdc](Cadence/GetFullDataForID.cdc) - Gets a more comprehensive set of data for a single NFT.
While we could use a single script to query for all the data, larger collections will cause the script to time out. Instead we query for just the data we need to reduce the chances of a timeout occurring.
//Check to see if the resource at this location is a subtype of NonFungibleToken.Collection.
if type.isSubtype(of: Type<@NonFungibleToken.Collection>()) {
//Add this path to the array
paths.append(path)
}
//returning true tells the iterator to continue to the next entry
return true
});
//Return the array that we built
return paths
}
We use the Storage Iteration API to look at everything the account has in it's storage and see if it is an NFT Collection. We return a list of all found NFT Collections.
We use this to create a list of collection paths a user can pick from. When the user selects a path to view, we fetch a list of IDs contained in that collection:
import NonFungibleToken from 0x1d7e57aa55817448
pub fun main(addr: Address, path: StoragePath) : [UInt64] {
//Get the AuthAccount for the given address.
//The AuthAccount is needed because we're going to be looking into the Storage of the user
var acct = getAuthAccount(addr)
//Get a reference to an interface of type NonFungibleToken.Collection public backed by the resource located at path
var ref = acct.borrow<&{NonFungibleToken.CollectionPublic}>(from: path)!
//Return the list of NFT IDs contained in this collection
This gives us a dictionary that maps NFT IDs to Display structs ({UInt64:MetadataViews.Display}). Because accessing this information can be tedious in C#, we can define some C# classes to make our lives easier:
public class File
{
public string url;
public string cid;
public string path;
}
public class Display
{
public String name;
public String description;
public File thumbnail;
}
This will allow us to use Cadence.Convert to convert from the CadenceBase that the script returns into a Display class.
This line in NFTViewer.cs is an example of converting using Cadence.Convert:
You might ask whey we don't combine GetNftIdsForCollection.cdc and GetDisplayDataForIDs.cdc to get the Display data at the same time we get the list of IDs. This approach would work in many cases, but when an account contains large numbers of NFTs, this could cause a script timeout. Getting the list of IDs is a cheap call because the NFT contains this list in an array already.
By getting just the NFT IDs, we could implement paging and use multiple script calls to each fetch a portion of the display data.
This example doesn't currently do this type of paging, but could do so without modifying the cadence scripts.
The end of NFTViewer.cs contains classes that we use to more easily convert from Cadence into C#. One thing to note is that the Cadence structs contain Optionals, like:
var IPFSFile: AnyStruct?
while the C# versions do not, such as
public IPFSFile IPFSFile;
This is because we are declaring them as Classes, not Structs. Classes in C# are reference types, which can automatically be null. We could have used Structs, in which case we'd have to use:
public IPFSFile? IPFSFile
This would wrap the IPFSFile struct in a Nullable, which would allow it to be null if the Cadence value was nil.
Another thing to note is the declaration of the C# File class:
public class File
{
public string url;
public string cid;
public string path;
public string GetURL()
{
if (string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(cid))
{
return $"https://ipfs.io/ipfs/{cid}";
}
return url;
}
}
Compare this to the File interface in the MetadataViews contract:
pub struct interface File {
pub fun uri(): String
}
The MetadataViews.File interface doesn't actually contain any fields, only a single method. Because only two things in MetadataViews implement the
File interface (HTTPFile and IPFSFile), we chose to combine the possible fields into our File class.
pub struct HTTPFile: File {
pub let url: String
}
pub struct IPFSFile: File {
pub let cid: String
pub let path: String?
}
This allows Cadence.Convert to convert either an HTTPFile or an IPFSFile into a File object. We can then check which fields are populated to determine which it was initially.
This works fine for this simple viewer, but a more robust approach might be to create a ResolvedFile struct in the cadence script which has a single uri field and populates it by calling the uri() function on whatever File type was retrieved.