03 Resources
3.6 Non Fungible Tokens (NFT)
Creating non-fungible resources with Scrypto is just about as easy as creating fungible resources. There are some nuances with non-fungible resource being that each non-fungible unit is unique and has its own data which we call
NonFungibleData. Non-fungible resources are therefore powerful as it allows us to represent just about anything in a digital format. These digital tokenization can accrue value, interact with other applications, and has its own behavior that make them feel alive. In this section, we will go over how to use the ResourceBuilder to create non-fungible resources by referencing to the Magic Card Scrypto code example.//Creating a Non-Fungible Resource with Initial Supply
Creating a non-fungible resource first starts with defining its
NonFungibleData. This data structure is a custom struct where we can specify its fields and value. It looks something like this:#[derive(ScryptoSbor, NonFungibleData)]pub struct MagicCard { rarity: Rarity, level: u8,}Notice that we have a call to the
derive macro above our struct. When compiling your package, cargo will see this line and automatically add methods to this structure so that it is compatible with the Radix Engine which allow us to use it as the data of non-fungible resources.Notice that we have fields such as
rarity. These values are a selection of enums and if you're not familiar with enums, the Rust handbook provides a section on enums.Defining the
NonFungibleData embeds each non-fungible unit that is minted from the non-fungible resource with unique that can represent whatever it is that you’d like to take shape. In our snippet example above, we want to represent a card trading game called MagicCard which has very simple data sets: the card’s rarity and level. Every non-fungible unit that is minted will have different values which make each non-fungible unit unique.//Non-Fungible Resource Definition
Constructing the non-fungible resource definition is slightly different from what we’re used to when creating a resource definition for fungible resource. For one, we need to specify the
NonFungibleLocalIdType and each supply of non-fungible unit needs to have its NonFungibleData populated.let special_magic_card: NonFungibleBucket = ResourceBuilder::new_integer_non_fungible::<MagicCard>(OwnerRole::None) .mint_initial_supply([ ( NonFungibleLocalId::integer(1), MagicCard { rarity: Rarity::MythicRare, level: 3, }, ), ( NonFungibleLocalId::integer(2), MagicCard { rarity: Rarity::Rare, level: 5, }, ), ]);Line 2: As always, we use the
When we’re creating the resource definition for a non-fungible, particularly if we desire for it to have an initial supply, we need to populate the
ResourceBuilder to create our non-fungible resource. However, take note that when call on the function from the ResourceBuilder for non-fungible resources, we also need to specify its NonFungibleLocalIdType. In our case, we want the id of our NFTs to be of the integer type so we call new_integer_non_fungible().Line 3-18: Here, we defined two non-fungible units that will be minted with our non-fungible resource creation. Each has its own NonFungibleLocalId and NonFungibleData populated.When we’re creating the resource definition for a non-fungible, particularly if we desire for it to have an initial supply, we need to populate the
NonFungibleData for each non-fungible unit we’d like to mint. There’s also a concept of NonFungibleLocalId here that we need to flesh out, so let’s go over that.//Non-Fungible Resource Identification
Creating a non-fungible resource will produce its resource entity with a
ResourceAddress and its own ResourceManager. However, this isn’t the non-fungible token (NFT) that many people refer to when speaking about it casually. The non-fungible resource represents the general collection whereas the non-fungible units minted from it are what truly equates to what is colloquially known as an “NFT”. Therefore, when defining a non-fungible resource, a NonFungibleLocalIdType needs to come with it and each non-fungible unit with also have a NonFungibleLocalId along with a ResourceAddress. When combined together, represent its NonFungibleGlobalId which generally looks like this ResourceAddress:NonFungibleLocalId.You saw above that we specified the special_magic_card will have a NonFungibleLocalIdType as an integer when we called on new_integer_non_fungible. There are a number of NonFungibleLocalIdType we can choose from:NonFungibleLocalIdType
Function
Description
Integer
new_integer_non_fungible
Represents an ID type using an integer up to u64.
String
new_string_non_fungible
Represents an ID type using a String matching [_0-9a-zA-Z]{1,64}
RUID
new_ruid_non_fungible
Represents an ID type using "Radix Unique IDentifier".
Bytes
new_bytes_non_fungible
Represents an ID type using Byte of length between 1 and 64.The choice which
NonFungibleLocalIdType to select for your non-fungible resource is up to you. Each have their own unique benefits. String types can provide interesting and descriptive identifiers, Integer can be used to order each non-fungible unit minted, or RUID type can be totally arbitrary.//Creating Non-Fungible Resource with a Mutable Supply
Non-fungible resources with no initial supply has no surprises. Of course, it’ll be good to apply a “mintable” resource behavior and apply the appropriate resource auth to do so. For the simplicity of our example, we’ll simply have it be mintable by anyone.
let magic_card: ResourceManager = ResourceBuilder::new_integer_non_fungible::<MagicCard>(OwnerRole::None) .mint_roles(mint_roles!( minter => rule!(allow_all); minter_updater => rule!(deny_all); )) .create_with_no_initial_supply();While not shown in this snippet, it’s standard practice for us to store the
ResourceManager in the component struct so that the component can refer to it when we create a method to mint non-fungible units.//Minting NFTs with the ResourceManager
Here, we’ll create a method
mint_magic_card to mint our magic_card.pub fn mint_magic_card(&mut self) -> NonFungibleBucket { // Mint a new card let magic_card_bucket = self.magic_card_manager.mint_non_fungible( &NonFungibleLocalId::integer(self.magic_card_id_counter), MagicCard { rarity: Rarity::Common, level: 1 }, ).as_non_fungible(); self.magic_card_id_counter += 1; return magic_card_bucket}Line 5: Note the inconsistency in how we specify the
It’s also worth noting when we specify the
NonFungibleLocalIdType where we use NonFungibleLocalId::integer as opposed to IntengerNonFungibleLocalId. In future release of Scrypto, this will be fixed.As we expect, we would need the ResourceManager of the magic_card non-fungible resource to mint each non-fungible unit by calling mint_non_fungible. Similar to creating a non-fungible resource with an initial supply, minting a non-fungible unit will require the NonFungibleLocalId and the NonFungibleData to be populated with each mint. It’s also worth noting when we specify the
NonFungibleLocalId that we pass a reference to self.magic_card_id_counter on Line 5. What’s happening here is that we’re having the component manage NonFungibleLocalId data by incrementing each mint as shown on Line 12.//Mutable NonFungible Data
There are times when we may want to change the value of the
NonFungibleData a non-fungible unit may have. If we expect to change the value of each field, there are two things we need to make sure:1.) We specify which data field is mutable by applying #[mutable] macro above the field(s) that needs to be mutable in your NonFungibleData structure.#[derive(ScryptoSbor, NonFungibleData)]pub struct MagicCard { color: Color, rarity: Rarity, #[mutable] level: u8,}Line 5-6: We ascribed
#[mutable] for level, allowing us to change its value.2.) We’ll also need to apply the resource behavior which allows us to update the non-fungible unit’s NonFungibleData by going back to our magic_card resource definition and adding that in.let magic_card: ResourceManager = ResourceBuilder::new_integer_non_fungible::<MagicCard>(OwnerRole::None) .non_fungible_update_data_roles(non_fungible_data_update_roles!( non_fungible_data_updater => rule!(allow_all); non_fungible_data_updater_updater => rule!(deny_all); )) .create_with_no_initial_supply();Line 8-9: We’re setting roles permission to
allow_all for simplicity.Once we’ve set those, we can now have our component update a non-fungible unit’s NonFungibleData.//Viewing, Utilizing, and Updating NonFungibleData
There are various ways to retrieve and update the NonFungibleData of a non-fungible unit and how it’s done all depends on your use case. Here, we have a method upgrade_my_card which takes in a NonFungibleBucket which is meant for a magic_card a user may send in.
pub fn upgrade_my_card(&mut self, magic_card_bucket: NonFungibleBucket) -> NonFungibleBucket { assert!( magic_card_bucket.amount() == dec!("1"), "We can upgrade only one card each time" ); assert!( magic_card_bucket.resource_address() == self.magic_card_manager.address(), "Only regular magic cards can be used to upgrade!" ); // Retrieve the `NonFungibleLocalId` and store in a variable for // convenience. let nft_local_id: NonFungibleLocalId = magic_card_bucket.as_non_fungible().non_fungible_local_id(); // Retrieve the `NonFungibleData` and store in a variable for // convenience. let mut non_fungible_data: MagicCard = magic_card_bucket.as_non_fungible().non_fungible().data(); // Retrieve reference of `ResourceManager` for `magic_card` and // update the `NonFungibleData`. self.magic_card_manager.update_non_fungible_data( &nft_local_id, "level", non_fungible_data.level += 1, ); return magic_card_bucket}Line 3-11: Creating assertions can be helpful to create helpful errors which expects a desired result.To update a
The
1. The
2. The
Both of which, we were able to retrieve from the
NonFungibleData, we’ll need to retrieve the ResourceManager which the NonFungibleBucket denoted by magic_card_bucket can be retrieved from, or since we conveniently stored the ResourceManager of our magic_card, we can also reference it that way as we’ve shown in Line 25.The
ResourceManager has a method update_non_fungible_data which requires two things:1. The
NonFungibleLocalId of the non-fungible unit we’d like to update.2. The
NonFungibleData of the non-fungible unit.Both of which, we were able to retrieve from the
magic_card_bucket that’s passed in.//Non-Fungible Resource Metadata and NonFungibleData
With a basic understanding of how
NonFungibleData works and how we can utilize it, there may come some questions around the distinction between a non-fungible resource’s metadata and a non-fungible unit’s NonFungibleData. Both concepts address similar things: to provide some information about this thing.However, it’s worth noting that the use of metadata is broad, whereas NonFungibleData are specific to the non-fungible unit. Let’s take a look at our special_card and apply some metadata to it.let special_magic_card: NonFungibleBucket = ResourceBuilder::new_integer_non_fungible(OwnerRole::None) .metadata(metadata!( init { "name" => "Russ' Magic Card Collection", locked; } )) .mint_initial_supply([ ( IntegerNonFungibleLocalId::new(1), MagicCard { rarity: Rarity::MythicRare, level: 3, }, ), ( IntegerNonFungibleLocalId::new(2), MagicCard { rarity: Rarity::Rare, level: 5, }, ), ]);When we apply metadata to a resource. The metadata can be used to convey what the resource is to the public. Particularly as it relates to non-fungible resource, it describes the entire collection. Whereas, each
NonFungibleData within a non-fungible unit, describes that particular unit that belongs to the non-fungible resource.//Full implementation
That’s the general walkthrough of how non-fungible resources work with Scrypto. There’s more interesting applications and mechanics to explore with non-fungible resources, but this gives a basic framework how to understand them. Below, you’ll find the full implementation of the example we used throughout this section, which you can copy into your own Scrypto package to explore.
use scrypto::prelude::*;#[derive(ScryptoSbor)]pub enum Rarity { Common, Rare, MythicRare,}#[derive(NonFungibleData, ScryptoSbor)]pub struct MagicCard { rarity: Rarity, #[mutable] level: u8,}#[blueprint]mod magic_card_nft { struct MagicCardNft { /// A vault that holds all our special cards special_cards: NonFungibleVault, /// The price for each special card special_card_prices: HashMap<NonFungibleLocalId, Decimal>, /// The resource address of all random cards magic_card_manager: ResourceManager, /// A counter for ID generation magic_card_id_counter: u64, /// A vault that collects all XRD payments collected_xrd: FungibleVault, } impl MagicCardNft { pub fn instantiate_component() -> Global<MagicCardNft> { // Creates a fixed set of NFTs let special_magic_card = ResourceBuilder::new_integer_non_fungible(OwnerRole::None) .metadata(metadata!( init { "name" => "Russ' Magic Card Collection", locked; } )) .mint_initial_supply([ ( IntegerNonFungibleLocalId::new(1), MagicCard { rarity: Rarity::MythicRare, level: 3, }, ), ( IntegerNonFungibleLocalId::new(2), MagicCard { rarity: Rarity::Rare, level: 5, }, ), ]); let magic_card = ResourceBuilder::new_integer_non_fungible::<MagicCard>(OwnerRole::None) .mint_roles(mint_roles!( minter => rule!(allow_all); minter_updater => rule!(deny_all); )) .non_fungible_data_update_roles(non_fungible_data_update_roles!( non_fungible_data_updater => rule!(allow_all); non_fungible_data_updater_updater => rule!(deny_all); )) .create_with_no_initial_supply(); // Instantiate our component Self { special_cards: NonFungibleVault::with_bucket(special_magic_card), special_card_prices: HashMap::from([ (NonFungibleLocalId::integer(1), dec!(100)), (NonFungibleLocalId::integer(2), dec!(200)), ]), magic_card_manager: magic_card, magic_card_id_counter: 0, collected_xrd: FungibleVault::new(XRD), } .instantiate() .prepare_to_globalize(OwnerRole::None) .globalize() } pub fn buy_special_card( &mut self, key: NonFungibleLocalId, mut payment: FungibleBucket, ) -> (NonFungibleBucket, FungibleBucket) { // Take our price out of the payment bucket let price = self.special_card_prices.remove(&key).unwrap(); self.collected_xrd.put(payment.take(price)); // Take the requested NFT let special_card_bucket = self.special_cards.as_non_fungible().take_non_fungible(&key); // Return the NFT and change (special_card_bucket, payment) } pub fn mint_magic_card(&mut self) -> NonFungibleBucket { // Mint a new card let magic_card_bucket = self.magic_card_manager.mint_non_fungible( &NonFungibleLocalId::integer(self.magic_card_id_counter), MagicCard { rarity: Rarity::Common, level: 1 }, ).as_non_fungible(); self.magic_card_id_counter += 1; return magic_card_bucket } pub fn upgrade_my_card(&mut self, magic_card_bucket: NonFungibleBucket) -> NonFungibleBucket { assert!( magic_card_bucket.amount() == dec!("1"), "We can upgrade only one card each time" ); // Retrieve the `NonFungibleLocalId` and store in a variable for // convenience. let nft_local_id: NonFungibleLocalId = magic_card_bucket.as_non_fungible().non_fungible_local_id(); // Retrieve the `NonFungibleData` and store in a variable for // convenience. let mut non_fungible_data: MagicCard = magic_card_bucket.as_non_fungible().non_fungible().data(); // Retrieve reference of `ResourceManager` for `magic_card` and // update the `NonFungibleData`. self.magic_card_manager.update_non_fungible_data( &nft_local_id, "level", non_fungible_data.level += 1, ); return magic_card_bucket } }}