Creating on-chain NFTs on Cardano

This guide brings together information I learned in creating my first on-chain collection, O. Community members I reached out to, namely Hookman (Block Clocks) and ThisCrazyLife (The Refresh), were tirelessly helpful throughout the process and creating this guide is my way of putting this shared knowledge to further use and sharing what I learned in the process.

This guide will take you through all of the steps required to create a ‘ready to mint’ JSON file, containing the metadata for your on-chain JavaScript based NFT.

This guide assumes that you already have, or can gain elsewhere, the JavaScript programming knowledge required to create your pieces and are familiar with programming environments. If you need a recommendation, my personal preference is for VS Code with the Live Server extension, which allows you to see the piece your’e working on updated as you’re working on it.

Setup

To start, you will need an html file containing some basic css to ensure that your NFT will display correctly. Below the code snippet you will find descriptions of each of the elements used and the reasons why.

                    
<!DOCTYPE html>
<html>
    <head>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    </head>
    <body>
        <style>
            * {
            padding: 0;
            margin: 0;
            overflow: hidden;
            }
        </style>
    
        <canvas id='myCanvas' style='object-fit: contain; width: 100vw; height: 100vh;'>
    Your browser does not support the HTML5 canvas tag.</canvas>
    
        <script>

        var canvas = document.getElementById('myCanvas');
        canvas.width = 4096;
        canvas.height = 4096;
        canvas.style.width = '100vw';
        canvas.style.height = '100vh';
        var ctx = canvas.getContext('2d');
        var cW = ctx.canvas.width;
        var cH = ctx.canvas.height;

        ...

        </script> 

    </body>
</html>

                    

The <style> section ensures that there is no blank space shown around your image by defining padding and margin as 0. It is also likely that scrollbars in the finished piece are undesirable, which is why we define overflow:hidden.

Next we create the canvas with a little more styling to ensure that the end NFT will make the most of the space available. We also state a short message here, informing people using incompatible browsers, or who have JavaScript disabled. You can leave the text in the canvas element out, but it is better for your collectors or viewers to be informed rather than being presented with a blank screen.

Up next is the script element where we first define a few variables which will help with the NFT coding process. canvas.width and canvas.height are set here to produce a square 4k image and you may adjust these as you see fit. I would advise considering how and where your pieces are most likely to be seen if you haven’t already decided on the size of your image. If you are looking at print sizing note that output will be at 96dpi and a 4k square image will print to 108 x 108cm without any pixelation.

Following the size definition we style the height and width using vh (viewport height) and vw (viewport width) values which ensure the image fills the viewport responsively. The number preceding vh and vw is the percentage you would like to fill the viewer by, hence 100vw and 100vh will fill 100% of the viewable width and height ensuring the image won’t extend beyond the viewable bounds of the window by automatically resizing.

We then state some shorthand variables for the canvas (ctx), canvas width (cW) and canvas height (cH) to ensure your code can be kept as light as possible. You may also change these, but be sure to use the same variable names throughout your code.

Coding your NFT

Directly below the statement of variables, in place of ‘…’ and before the closure of the script element (</script>) is where you should include the entirety of the JavaScript which forms your NFT. It is important that you keep everything in a single document (ie do not link to an external JavaScript file) and do not use frameworks such as p5.js. Although highly versatile, using most frameworks results in a file that is too big to be sent over the Cardano network.

Since we are displaying the NFT responsively, you should think about coding responsively. This is where cW and cH come in useful because rather than defining a set size and weight for elements, a responsive piece will have objects sized in relation to the size of the canvas. For example, a circle centred on the canvas with a radius 1/4 of the canvas width might be coded as follows:

                    
ctx.lineWidth = canvasW * 0.00333;
ctx.beginPath();
ctx.arc(cW / 2, cH / 2, cW / 4, 0, 2*Math.PI);
ctx.stroke();

                    

Note that I have also set the line width or stroke weight in relation to the canvas to ensure proper scaling.

Be sure to check the results of your coding regularly. Using the aforementioned Live Server extension in conjunction with VS Code or simply double clicking the html file in explorer to open the in your browser of choice are great ways of doing so (note that the latter method will not update as you add to your code).

Minification

In order to keep your code light and the file size as small possible it is good practice to minify your JavaScript. A smaller file carries a lower transaction cost and will likely render faster, so it is in everyone’s interest to keep things as light as you can.

I recommend making a copy of your original html file here, just in case anything goes wrong. From here you can copy all of the code between the elements (and not the elements themselves), paste them into an online minifier, minify the script, copy the result and paste it in place of the script you copied.

My personal preference is for Toptal’s online tool, but many others are available.

Conversion to base64

Now that we have a fully working, minified JavaScript artwork encapsulated in an html file, we need to encode it in such a way that it can be transmitted without loss of integrity and embedded within your metadata. This is where base64 comes in.

For our metadata purposes we also need to ensure that no single string is longer than 64 characters, which means we will need to break up the base64.

Online encoders are available, however this Python script, which I have made available via Google Colaboratory provides something of a shortcut, encoding the document and breaking it into strings not longer than 60 characters, inside an array, ready to be pasted into your metadata.

Compiling the metadata

The metadata file will take the form of a JSON and it will contain your base64 array and everything you want to tell the world about your NFT.

Please refer to the CIP 25 NFT Metadata Standard for guidance on setting up your metadata. The following takes the general structure outlined in the standard and shows the mediaType which should be stated and where your base64 array should be included (note the “data:text/html;base64,” prefix which is automatically generated by the Python script linked above):

                    
{
    "721": {
        "<policy_id>": {
        "<asset_name>": {
            "name": <string>,
    
            "image": <uri | array>,
            "mediaType": image/<mime_sub_type>,
    
            "description": <string | array>,
    
            "files": [{
                "name": <string>,
                "mediaType": "text/html",
                "src":[
                "data:text/html;base64,",
                "...","..."
                ]
            }],
            <other properties>
            }
        },
        "version": <version_id>
    }
}

                    

If you wish to include an IPFS thumbnail image, simply replace <other properties> with the following:

                    
"image": <IPFS link>,
"mediaType": "image/png"

                    

To make sure the metadata and piece are displaying correctly, I strongly recommend using the pool.pm metadata test page. You can experiment with re-ordering items to some extent by capitalising the start of a field name (such as “Name” rather than “name”). Capitalised fields will display before others, in alphabetical order. The metadata file can then be saved in a text document with the extension .json

Congratulations, you’re now ready to mint your on-chain NFT.