The guide to learn everything about on-chain attributes for NFT on Elrond

Horizon Labs
11 min readMay 23, 2022

--

Introduction

We met a lot of people/projects who wanted to make staking or little games with their NFTs based on their attributes but more often than not, they didn’t create their NFT with on-chain attributes, and when they did, it wasn’t in the most efficient manner. Most people simply don’t know what is the point of getting their NFT’s attributes on-chain and how to do it.

This article will offer you a guide to explain what on-chain attributes are, how to create NFTs with and how to manipulate them.

What is the difference between on-chain and off-chain attributes?

On-chain attributes are, as their name indicates, attributes that are stored directly in the blockchain. In contrast, off-chain attributes are simply a “link” (usually a CID “Content Identifier”) to a JSON file containing attributes stored on a centralized server, IPFS or other solutions.

The difference between the two is that if the attributes are stored in the blockchain then it is possible to read them and manipulate them in smart contracts in order to be able to perform actions based on the value of the attributes whereas in the second case, it is not possible to read the JSON file which is stored off-chain and therefore to manipulate them.

When do we need on-chain attributes for an NFT?

As explained above, we need to have on-chain attributes if we need to read or manipulate them, whether to verify information, modify a specific attribute and/or take actions based on the value.

For example, if you want to make a game with characters that fight each other in turn, each character has attributes, whether it be power, level, agility or resistance.

Let’s say you have to read for example the power value of NFT A and the power value of NFT B and the one who has the most power wins the fight and with each winning fight his level increases, you will need to have on-chain attributes because it is a value you want to A) keep track of and B) be able to update several times.

Whereas in the case of an NFT that is just an artistic NFT and you think you will never need to read its attributes into a smart contract, there is no need to store the attributes in the blockchain.

What is the current problem with on-chain attributes on Elrond?

Since the beginning of NFTs on Elrond, on-chain attributes are a long string in the form of a ManagedBuffer (the managed type for a string) which is composed of trait types (keys) and values for attributes separated by “;” and “:”, it looks like this:

background:ocean;skin:my skin;color:blue;accessories:gun

The problem is that ManagedBuffers are not a regular strings, some really useful methods available for strings cannot be used with ManagedBuffer especially parsing methods. Even if you convert it to another type, it complicates the read/modify task and it’s simply not the most efficient way to have on-chain attributes.

The second problem is that no marketplace supports true on-chain attributes for NFTs. This is a real problem because “we” (the developers) have to do a lot of unnecessary work to get the marketplace to display the attributes of our NFTs and we have to do extra work especially if we want to update the attributes and the marketplaces also display the new attributes.

The guide to having on-chain attributes for your NFT

In this guide, you will learn how to create NFTs with true on-chain attributes in 2 different ways, you will learn how to read them in a smart contract as well as how to update them and the marketplaces will display your attributes, you will be a master in handling on-chain attributes.

Let’s go into it.

In our smart contract, we will create a struct called NftAttributes which has the different possible traits of the NFT and a metadata field.

In this example, all our traits are ManagedBuffers and a u16 (for a number) but we can also do it with Enums, that will work too.

As said above, the marketplaces will not display our attributes, so this is the reason why we have a “metadata” field, you will have to upload your json (with your attributes) on IFPS and then put the IPFS cid in the metadata field in this format: metadata:<cid>..

Keep in mind that you only need the metadata field for the marketplaces to display your attributes (they will display the attributes of the JSON you uploaded to IPFS). So if you don’t need the marketplaces to display your attributes or if the marketplaces support true on-chain attributes, you can remove it.

So, as you have understood, since no marketplace supports true on-chain attributes, we need to do a mix of on-chain and off-chain attributes (the on-chain attributes to read and manipulate the attributes in smart contracts and the off-chain attributes in the metadata field for the marketplaces to display the attributes).

We’re going to work with marketplaces to find a smart way to support true on-chain attributes.

Create an NFT with on-chain attributes

There are two ways to create an NFT with on-chain attributes:

  1. For the first one, you will have to pass all the trait values of your NFTs in the arguments of the function that will mint the NFT.
  2. For the second one, you will have to fill all your attributes in your SC storage and you will read these values when you call the mint function.

Let’s start with the first one.

Create an NFT with attributes values in arguments

What better way to explain it than with a function example? Here’s the createNft function that creates an NFT with on-chain attributes.

We pass the name we want for our NFT (you can also have the name in storage and just add the NFT number next to it) and all the trait values for our attributes in the function arguments. Now we just need to fill the struct with our values, but before we need to build the metadata. If the number is 1, the metadata field will look like this:

metadata:<cid>/1.json

Once the struct has been filled with all the values and stored in the attributes variable, we have one last thing to do and that is to create the URI, otherwise we will have no image in our NFT (You need to upload your images to IPFS and set the image CID in the smart contract storage).

And finally, once everything has been created and filled in, we can create our NFT, we use the send().esdt_nft_create() function to do this, we filled in the args with everything and it’s done! Our NFT with on-chain attributes has been created.

Remember that you must issue the collection token and set the NFTCreate role beforehand, otherwise you will not be able to create the NFT.

Here is also an example of transaction that should be done to call this function:

Create an NFT with attributes from storage

The second one is done in 2 steps, first we will have to fill all the attributes of our NFTs in the smart contract storage and then we will have to create the NFT by getting the attributes from the storage.

Your smart contract doesn’t have information about the values of your attributes, so you will need to “push” them into it but to do this you will need to perform transactions with the attribute values in the data.

We do it with the fillAttributes function, it’s a simple function that will create the attributes struct for an NFT, the number is its ID.

This function set the NftAttributes struct for an NFT in the attributes mapper that you will use when creating the NFT.

Here is also an example of transaction that should be done to call this function:

Once you have filled the attributes of your NFT, we can now create it. To do so, we have a function called createNftWithAttributesFromStorage, this function does exactly the same thing as thecreateNft function we see above, except that we get the attributes from the storage.

Is one method better than the other?

We always recommend using the first method, because in the second method, you’ll have to do a ton of transactions to fill the attributes, and then a ton more transactions to create the NFT (even if you work with MultiValueEncoded).

The only reason you can use the second method is if you want your users to mint your NFTs instead of you minting them, for a sale for exemple, if you don’t want to pre-mint the NFTs and then sale them, you will have to use the second method.

Decode the attributes

Once we are done and the NFT is minted we can decode its attributes to verify if everything worked:

To do this, we use the get_tokens_attributes function of the NonFungibleTokenMapper, it returns the struct NftAttributes, we just have to get the field we want to get the value we want.

We recommend using NonFungibleTokenMapper, as it saves you quite a few lines of code, however you can also decode the attributes without having to use it. You can find more information about the NonFungibleTokenMapperin the Elrond Docs here.

Note that the smart contract must hold the NFT to decode its attributes.

Verify the attributes

We can also check that the marketplaces display the attributes of our NFT:

We can also take a look at the Elrond API to see if it also displays our metadata:

Update the attributes

We have often heard the question “can we update the NFT attributes?” or even “is it possible to update the NFT attributes?” — The answer is yes and there is 2 ways to do it depending on what you want to do:

  1. You want to update only the attributes
  2. You want to update the image AND the attributes

Let’s start with the first situation.

Update only the attributes

There is a “level” field in our NftAttributes struct so let’s increase the NFT level by one.

First, we need to read the attributes of the NFT we want to update because we want to know its level and the other information because we will not change them.

Once we have the attributes, we create a new nftAttributesstruct stored in the variable new_attributes, we only change the level field by adding one to the previous one and we pass on the information of the old attributes for the other fields.

Then, we use the send().update_attributes() function to update the NFT attributes with the new attributes and it’s done, your NFT attributes have been updated!

Note that the smart contract must hold the NFT to read and update the attributes and it must also have theNFTUpdateAttributesrole set.

But as you probably understood, the metadata field has not been updated, so the marketplaces still show the old level of the NFT, that’s why I say it’s a big problem that the marketplaces don’t support real on-chain attributes, because every time we want to update the attributes we also have to generate a new JSON, upload it to IPFS and then add it into the NFT.

Update the image AND the attributes of an NFT

For updating the image and attributes there are also two solutions, but one of them is quite bad.

Just above you learned that there is a function to update the attributes so you already know how to update the attributes of an NFT, and if you don’t know it yet, there is also a function to add a URI (so it could be an image), so it’s simple you just have to do as above but with the add URI as well!

Well, no.

As the name suggests, the function adds URIs but it only adds URIs, so yes you can add one or two or even three images, but the first one will not be removed and can’t be deleted. And as a result, the Elrond API, Maiar and the marketplaces always display the first image and not the new ones.

But for every problem, there is a solution. The solution for updating the image and attributes is to burn the old NFT and mint a new one with the new image and attributes..or have a centralized image in the NFT (but we won’t go into that).

Let’s assume that all NFT values will be modified and explore this example:

Even if all the values will be modified, we read the old NFT attributes because we want to increase the level by one.

Once we getted the old NFT attributes, we burn the old NFT and we create the new nftAttributes struct with our new fresh values.

Then, we the push the new URI (image) and we finally create the NFT with the new image and the new attributes (with the new metadata field as well), you start to know.

But, we have a major problem here, the new_image_uri and the new_metadata come from the transaction arguments and the last thing you want is for your user to have the image and metadata they want, so the image and metadata should never be in the arguments. But we’re not going to go into that too much so as not to complicate the article.

The same goes for the value of your attributes, they should never be in the arguments if it’s callable by anyone, otherwise they can do the NFT with the attributes they want, even if we don’t go further in this article, keep it in mind.

The smart contract must own the NFT to be able to read the attributes and burn it. It must also own the NFTBurn and NFTCreate roles in order to burn and mint.

Conclusion

Voilà, it was that simple, wasn’t it? Now I hope you are a master in on-chain attributes for NFT on Elrond!

You know how to have on-chain attributes that you can use in your smart contracts for different use-cases while having your attributes visible on the different marketplaces.

If things were not clear for you or if you want to start and test using our examples, we have uploaded everything on Github so you can check it out: https://github.com/horizonnlabs/on-chain-attributes

There are examples of what we have shown as well as mandos tests and a set of interaction scripts (with erdpy) so you can deploy this on the different networks, you are free to use our examples in your code.

Thank you for reading.

It’s time to build.

--

--

Horizon Labs

Offering consulting and technical services (smart contracts, dApps and back-end solutions) to awesome projects on MultiversX (previously Elrond).