NXTER.ORG

NFTMagic: NFT on Ardor. How does it work

In this post I would like to explain how the NFTMagic.art website works and how it takes advantage of the Ardor features to store large NFT files and their metadata completely on chain.

Following the DeFiMAGIC bot NFT functionality described here, I wanted to implement the idea of Sergi Baila from Jelurida to improve the way to store the NFT data on the blockchain.

So what was that idea. Let’s clarify.

This is how the DeFiMAGIC bot NFT function works, in a nutshell:

– split the base64 data of the NFT into 41KB chunks

– store those chunks in the data-cloud

– track the fullHashes of those data-cloud transactions in the correct sequence

– create the asset adding to the description, one after the other, in the right sequence, the fullHashes of the data-cloud chunks.

When reading the NFT the bot would find the asset, read the description, split that text into 64 bytes chunks (64 bytes is the size of each fullHash), read the corresponding data-cloud chunks and concatenate them to recreate the initial image (NFT).

With this solution it’s possible to store 1000 / 64 = 15 data-cloud fullHashes. (1000 is the max asset description size). In terms of size that means 615KB of data.

Awesome, no? But what can we improve:

1 – when issuing a singleton asset the cost of the asset is much lower than a normal asset, but only if its description is under 160 characters

2 – 615KB is good enough… but could be more if necessary

So how do we improve our “bot way” to solve these 2 issues.

The solution is to put the fullHash of the data-cloud chunks in a sort of domino fashion adding the fullHash of the following data-cloud chunk into the previous data-cloud chunk, effectively needing only to store the first fullHash in the asset description. That would mean staying under the 160 bytes description and making singleton assets cheap to issue no matter the size of the NFT.

For backend processing convenience the following format has been used for the asset description:

<fullHash of first data-cloud chunk – 64bytes><fullHash of last data-cloud chunk – 64bytes><seriesID – 2bytes><seriesSize – 2bytes><seriesName – 28bytes>

If you sum those sizes you get 160bytes.

You can ignore for the moment the “<seriesID – 2bytes><seriesSize – 2bytes><seriesName – 28bytes>”, this will be explained later on.

When uploading a NFT via nftmagic.art what happens is that:

– the NFT file is converted to base64, split into chunks of 41KB

– The last data-cloud chunk is the first to be uploaded

– The fullHash of the last data-cloud chunk is added to the second-last data-cloud chunk description

– The fullHash of the second last data-cloud chunk is stored into the third-last data-cloud chunk

– and so on… until the first data-cloud chunk is loaded with the description containing the second data-cloud chunk fullHash.

– The asset is created with the description containing the the information formatted as described above:

<fullHash of first data-cloud chunk – 64bytes><fullHash of last data-cloud chunk – 64bytes><seriesID – 2bytes><seriesSize – 2bytes><seriesName – 28bytes>

When the NFT is loaded, we will find in the description of the asset the first data-cloud chunk fullHash and the last, so we know when to stop concatenating our base64 data.

We have now improved those 2 points mentioned above:

– asset description is under 160bytes

– size is theoretically unlimited because we can apply the “domino” fullHash way as much as we want.

But there is more needed in a NFT. And thanks to community members Bitswift Paul, Apenzl and Shugo we came up with the actual metadata needed to fully define a NFT.

That is:

– Name of the NFT

– Description of the NFT

– Does it belong to a series (or collection)

– How big is this series

– How many exist of each NFT

– Which categories does it belong to

Now that our asset description field was taken, it was necessary to find another way.

But in our model the last data-cloud chunk will always be there, also for a NFT with size of 1 byte. So that was the place to pack more data!

And here is where we actually store some of the data above:

– Name of the NFT: Stored into “Type” field (100 bytes) of the data-cloud. But why not in the asset name field? The reason is that the asset name field is only 10 bytes long, therefore 100 gives more flexibility.

– Description of the NFT: Stored into the “Description” field (1000 bytes) of the data-cloud

– Which categories does it belong to: Stored as sequence of “binary” 1 and 0 into the “Channel” field (100 bytes). This means that nftmagic knows that if the last digit of this string is 1 then the NFT belong to category “Art”. For example a “channel” field containing 1010 means that the NFT belongs to categories Art and Collectibles.

The “name” field of the asset of data-cloud is not used to store relevant data as it’s only 10 bytes long therefore it contains a random string starting with NFTxxxxxxx. For series the name will be random but keeping the same root with an increasing index. Therefore all NFTs in the same series will have names like NFTskdkd01, NFTskdkd02, NFTskdkd03 so it’s easier to understand that they belong to the same series.

If we compare with our list of meta-data we are missing… let’s see…

– Does it belong to a series (or collection)

This point was solved by adding those additional elements to the description of the asset:

<seriesID – 2bytes><seriesSize – 2bytes><seriesName – 28bytes>

– seriesID is the “place” in the series the NFT belongs

– seriesSize is the size of the series, so when creating a new series, the user has to define at the beginning how big this series will be. For example we want to create a series of 10 fruits, when we define 10 that cannot be changed afterwards. For all NFTs in the same series this value remains always the same

– SeriesName is the name of the series, for example “Fruits”. This also remains the same for all NFTs in the same series

So that piece of the asset description could look like “….0110Fruits” or “….0510Fruits” or “….1010Fruits”. 

When a user goes on nftmagic.art, the form will ask if the NFT belongs to a series and if it belongs to a new series or existing one.

If the user chooses new series, then the name of the series and the size of the series have to be added to the form.

When submitting the first NFT is that series is created with the seriesID 01.

When the user chooses to add a NFT to an existing series, the form will search the all the assets of the user filling the form with description over 128bytes and check for that specific description format mentioned above.

If the description matches that format, the page will extract the series names and propose them in a dropdown menu.

When the user chooses for example the Fruits series from that list, then the backend will automatically find the highest seriesID for that seriesName, scanning the user assets, and automatically assign for the new NFT the new seriesID.

When the series is complete. Eg, we have already uploaded 10 NFT that belong to the “Fruits” series, then the application will inform the user that the series is complete.

– How big is the series

This is found in the asset description in the 2 bytes defining the seriesSize

– How many exist of each NFT

This is easy. When issuing the asset we simply issue the asset with more than 1 unit and 0 decimal places. This will increase the cost of issuing the asset.

There are certainly many other ways one could implement what is done with NFTMagic, but this is now under test and seems promising and could become a standard for other projects wanting to create NFT platforms on Ardor.

Little note on the format of the base64 data. As PHP seems to not like much certain special characters and therefore the base64 is slightly modified before splitting it and uploading it. For the techies:

$base64=str_replace([‘+’, ‘/’, ‘=’], [‘-‘, ‘_’, ”], $base64);

When reading the data:

$base64=str_replace([‘-‘, ‘_’], [‘+’, ‘/’], $base64);

So this is something to consider when reading directly the data from the blockchain.

Little note about the upload form on the homepage of nftmagic.art: just right click and view page source to see how it works. The JS code is readable by anyone.