Build an AI NFT minting app

Build an AI NFT minting app

¡

11 min read

What are we building?🤔

We’ll be building and shipping a full-stack app that takes in prompts from users and generates art using stable diffusion and mint this generated art on the blockchain for free.

We are going to use hugging face to use the stable diffusion model and generate the art via an API and mint this generated art on the blockchain using the NFTport API to mint the NFT gasless, i.e. for free.


👀 Prerequisites

No prerequisites for the project, this is beginner friendly project and anyone can build this:)


⚙️ Let’s setup our React app

Feel free to skip this part if you already know how to create a new react app, you can also clone the starter template from here → https://github.com/0xblocktrain/ai-art-gasless-mint/tree/starter

We’ll be setting up our react app with tailwind CSS and a few other dependencies that we’ll use later.

npx create-react-app ai-art-gasless-mint

Run this command in your terminal, this will create a react app with the folder name ai-art-gasless-mint you can name it with whatever you want it to be.

Now let’s cd(change directory) into the app and then install the tailwind css

cd ai-art-gasless-mint

For installing tailwind you can follow this step-by-step guide given by tailwind to install it → Guide

If you were successful in installing the tailwind CSS, let’s next clean up our folder structure.

https://i.imgur.com/v4scFBf.png

Delete these two files as we won’t be needing this and also delete App.css and also clean up our App.js and remove the boilerplate code which comes with React App.

Now open your terminal again and start the react app

https://i.imgur.com/aVSPN6Q.png

Viola, we’ve got our React app working.

Next what we do is give the user the ability to write a prompt and generate an image using the stable diffusion model for which we’ll be using hugging face


🖼️ Generating AI art based on prompts

Let’s go to hugging face first and create a new account in order to get our API key.

https://i.imgur.com/4eLSIqb.png

Go over to Huggingface/join and then add your email, and password and verify the email address in order to get the API key or access token.

Once you have your email verified you should see this screen

https://i.imgur.com/OHoR1SO.png

We’ll be using https://huggingface.co/runwayml/stable-diffusion-v1-5 this model to generate our AI art based on the prompts you can even try this on the hugging face website itself

https://i.imgur.com/21KDcpO.png

The first load might take time as the model needs to be loaded first.

Let’s generate an access token next for us to generate images on our Frontend instead of everytime using the Huggingface for generating image, we need the access token/api key for using the hugging face interference api, you can read more about it over here → https://huggingface.co/docs/api-inference/index

Click on the top right corner where you have your avatar and click on Settings

https://i.imgur.com/PFxWJ38.png

Now click on Access token on the left sidebar and generate a new token

https://i.imgur.com/xpbTGfD.png

https://i.imgur.com/hRPNNan.png

Now copy the token and then open your react codebase on any editor you like, I’ll go ahead with visual studio code.

https://i.imgur.com/sJ0mgIH.png

Now create a .env file at the root of your app and create a new React environment variable to store the hugging face access token.(remember REACT_APP prefix is mandatory when using env variables in react)

https://i.imgur.com/xejo5A5.png

Now let’s create two states in our App.js one to store the input from the user and one to store the image data fetched from the API, but in order to fetch from the API we need axios package first let’s go back to our terminal and install it using the npm

npm install axios

Once we have our axios installed let’s go ahead and code in our App.js

  1. Create an input box in the middle and next button beside it.

  2. Create a state that will store the input text.

  3. Write a function to generate the ai image by calling hugging face api

  4. Store the response from API in a new state

This will be our workflow.

import { useState } from "react";
import axios from 'axios'

function App() {

  const [prompt, setPrompt] = useState("")

  console.log(prompt)

  return (
    <div className="flex flex-col items-center justify-center min-h-screen gap-4">
      <h1 className="text-4xl font-extrabold">AI Art Gasless mints</h1>
      {/* Create an input box and button saying next beside it */}
      <div className="flex items-center justify-center gap-4">
        <input
          className="border-2 border-black rounded-md p-2"
          onChange={(e) => setPrompt(e.target.value)}
          type="text"
          placeholder="Enter a prompt"
        />
        <button className="bg-black text-white rounded-md p-2">Next</button>
      </div>
    </div>
  );
}

export default App;

Here we use on change on the input to keep track of the text entered by the user

https://i.imgur.com/FkITt2O.png

You can see that our text gets logged here, what we do next is create a function to call the hugging face API and then store the response data in a state so that we can display the image to be minted by the user.

The endpoint for the API is in the hugging face docs→ https://huggingface.co/docs/api-inference/quicktour

https://i.imgur.com/NS72LU3.png

The model ID is on the models page → https://huggingface.co/runwayml/stable-diffusion-v1-5

https://i.imgur.com/mHQvE8E.png

This entire text is our model id.

Now let’s write a function to call the api and get the response.

const generateArt = async () => {
        try {
            const response = await axios.post(
                `https://api-inference.huggingface.co/models/runwayml/stable-diffusion-v1-5`,
                {
                    headers: {
                        Authorization: `Bearer ${process.env.REACT_APP_HUGGING_FACE}}`,
                    },
                    method: "POST",
                    inputs: prompt,
                },
                { responseType: "blob" }
            );
            const url = URL.createObjectURL(response.data)
            console.log(url)
        } catch (err) {
            console.log(err);
        }
    };

We call the API with the prompt as input and then get the blob from the response, now let’s create a URL from the blob so that we can use this as a source for our image tag.

Now let’s create a new state and store the blob in the state

const [imageBlob, setImageBlob] = useState(null)

const generateArt = async () => {
    try {
        const response = await axios.post(
            `https://api-inference.huggingface.co/models/runwayml/stable-diffusion-v1-5`,
            {
                headers: {
                    Authorization: `Bearer ${process.env.REACT_APP_HUGGING_FACE}}`,
                },
                method: "POST",
                inputs: prompt,
            },
            { responseType: "blob" }
        );
        console.log(response);
        const url = URL.createObjectURL(response.data)
        // console.log(url)
        console.log(url)
        // Set state for image
    setImageBlob(url)
    } catch (err) {
        console.log(err);
    }
};

Let’s use this image blob in the UI

return (
    <div className="flex flex-col items-center justify-center min-h-screen gap-4">
        <h1 className="text-4xl font-extrabold">AI Art Gasless mints</h1>
    <div className="flex flex-col items-center justify-center">
      {/* Create an input box and button saying next beside it */}
      <div className="flex items-center justify-center gap-4">
        <input
          className="border-2 border-black rounded-md p-2"
          onChange={(e) => setPrompt(e.target.value)}
          type="text"
          placeholder="Enter a prompt"
        />
        <button className="bg-black text-white rounded-md p-2">Next</button>
      </div>
      {
        imageBlob && <img src={imageBlob} alt="AI generated art" />
      }
    </div>
    </div>
);

Before testing this out we need to add onClick to our button and map the generateArt function to it.

return (
    <div className="flex flex-col items-center justify-center min-h-screen gap-4">
        <h1 className="text-4xl font-extrabold">AI Art Gasless mints</h1>
    <div className="flex flex-col items-center justify-center">
      {/* Create an input box and button saying next beside it */}
      <div className="flex items-center justify-center gap-4">
        <input
          className="border-2 border-black rounded-md p-2"
          onChange={(e) => setPrompt(e.target.value)}
          type="text"
          placeholder="Enter a prompt"
        />
        <button onClick={generateArt} className="bg-black text-white rounded-md p-2">Next</button>
      </div>
      {
        imageBlob && <img src={imageBlob} alt="AI generated art" />
      }
    </div>
    </div>
);

Viola🎉 here we have our AI art being generated

https://i.imgur.com/bLSRgNg.png

Now let’s create a flow to let user’s mint their AI generated image.


📈 Upload our AI-generated art to IPFS

Before we mint our AI-generated art as NFTs we’ll need to upload it somewhere so that our NFT’s metadata points to it, we’ll be storing our AI generated image on IPFS, IPFS is interplanetary file storage system which is actually a peer to peer protocol used for saving data, you can think of it as Torrent. You can read more about how it works over here → ipfs.tech/#how

We’ll be using NFT.storage for storing our files on IPFS.

Go over to Nft.storage/login and sign in using either github or your email.

https://i.imgur.com/NvN7lS5.png

Once you are logged in, click on the API keys tab on the top

https://i.imgur.com/Cw4RgkS.png

Next create a new key and copy the key and store it in our .env file at the root of the project with name REACT_APP_NFT_STORAGE

https://i.imgur.com/7Dd655w.png

Now go back to our terminal and install nft.storage package.

npm i nft.storage

Now let’s go ahead and create a function that uploads the file to IPFS, if you want to figure this out yourself feel free to read the docs of nft.storage → https://nft.storage/docs/#using-the-javascript-api

Remember we convert our image to a blob url in order to display it in the UI in the generateArt function, we’ll need to convert that blob to a file type before converting it to a blob url and store it in a state so that we can use it in our uploadArtToIPFS function. Let’s create a new state called file and update our generateArt function.

const [file, setFile] = useState(null)

const generateArt = async () => {
    try {
        const response = await axios.post(
            `https://api-inference.huggingface.co/models/runwayml/stable-diffusion-v1-5`,
            {
                headers: {
                    Authorization: `Bearer ${process.env.REACT_APP_HUGGING_FACE}}`,
                },
                method: "POST",
                inputs: prompt,
            },
            { responseType: "blob" }
        );
        // convert blob to a image file type
        const file = new File([response.data], "image.png", {
            type: "image/png",
        });
        // saving the file in a state
        setFile(file);
        const url = URL.createObjectURL(response.data);
        // console.log(url)
        console.log(url);
        setImageBlob(url);
    } catch (err) {
        console.log(err);
    }
};

Now that we have our image file let’s create a function to upload this to IPFS

import { NFTStorage } from "nft.storage";

const uploadArtToIpfs = async () => {
  try {

    const nftstorage = new NFTStorage({
            token: process.env.REACT_APP_NFT_STORAGE,
        })

    const store = await nftstorage.store({
      name: "AI NFT",
      description: "AI generated NFT",
      image: file
    })

    console.log(store)

  } catch(err) {
    console.log(err)
  }
}

We first need to import the NFT.storage package at the top and then initialize it in the function itself with the API key that we created earlier on the portal.

Next we use the store function from the NFT.storage package and use it to store the files, then we just console log it for now.

Now let’s map this function to a button.

{imageBlob && (
    <div className="flex flex-col gap-4 items-center justify-center">
        <img src={imageBlob} alt="AI generated art" />
        <button
            onClick={uploadArtToIpfs}
            className="bg-black text-white rounded-md p-2"
        >
            Upload to IPFS
        </button>
    </div>
)}

We’ll display it right beneath our generated image.

https://i.imgur.com/oOhIIzi.png

Now that we have our image being uploaded to IPFS and we are also getting it’s CID, CID is nothing but an content identifier for our file on the IPFS network, a quick google search might help you understand it better.

We’ll return IPFS CID from this function and next let’s mint our AI generated art as an NFT, but if you see the href generated it’s in the form ipfs:// which is not an actual url but we need an actual https url to mint the NFT, let’s use one of the IPFS gateways to create a url, we’ll write a function to replace ipfs:// with one of the IPFS gateways.

const cleanupIPFS = (url) => {
  if(url.includes("ipfs://")) {
    return url.replace("ipfs://", "https://ipfs.io/ipfs/")
  }
}

IPFS gateways are in the form

https://ipfs.io/ipfs/<YOUR-IPFS-HASH>

https://cf-ipfs.com/ipfs/<YOUR-IPFS-HASH>

There are multiple different gateways, a quick google search might help you understand more about this.

Now let’s use the cleanupIPFS function before returning the url

const uploadArtToIpfs = async () => {
  try {

    const nftstorage = new NFTStorage({
            token: process.env.REACT_APP_NFT_STORAGE,
        })

    const store = await nftstorage.store({
      name: "AI NFT",
      description: "AI generated NFT",
      image: file
    })

    return cleanupIPFS(store.data.image.href)

  } catch(err) {
    console.log(err)
    return null
  }
}

⚒️ Let’s mint our AI generated art for free

Let’s go to NFTport.xyz and get our free API key first

https://i.imgur.com/axAjFfv.png

You’ll get a email verification link verify it, and you’ll find a link to your dashboard in the email itself which contains the API key

https://i.imgur.com/FR6u8UM.png

Now let’s copy the API key and store it in our .env file at the root of the project with the name REACT_APP_NFT_PORT.

Now let’s write our function to mint NFTs for free on the Polygon Network , we’ll use NFTport’s easy mint API to mint our NFT’s you can read more about it on their docs → https://docs.nftport.xyz/reference/easy-minting-urls

https://i.imgur.com/Tuxzuxm.png

Now we’ll use axios to call this API with all our data and mint the NFT, let’s write a function for this:

const mintNft = async () => {
    try {
        const imageURL = await uploadArtToIpfs();

        // mint as an NFT on nftport
        const response = await axios.post(
            `https://api.nftport.xyz/v0/mints/easy/urls`,
            {
                file_url: imageURL,
                chain: "polygon",
                name: "Sample NFT",
                description: "Build with NFTPort!",
                mint_to_address: "0x40808d4730aeAAfb44465c507499CB8EE690497b",
            },
      {
        headers: {
          Authorization: process.env.REACT_APP_NFT_PORT,
        }
      }
        );
        const data = await response.data;
        console.log(data);
    } catch (err) {
        console.log(err);
    }
};

Instead of uploading the file to IPFS manually using a button we’ll call it directly before minting the NFT and get the image url.

Now let’s post a req with our API key and the required data to NFTPort.

Here, give a random name, description and give a wallet address that you currently have on the metamask.

Let’s map this function to a button, let’s convert the upload to ipfs button to a mint button as we already upload it while minting.

Once the NFT get’s minted you’ll get it logged in the console

https://i.imgur.com/XXWIVvh.png

Now head over to opensea.io and connect your wallet and go to your profile and then hidden tab

https://i.imgur.com/Mdl16ij.png

https://i.imgur.com/jrDNovT.png

https://i.imgur.com/rAxFaTr.png

Viola there you go we have our AI generated image minted as an NFT on Polygon network for free without paying a gas, you might be wondering why is this in the hidden tab, Opensea by default puts all the NFTs minted on polygon chain in the hidden tab you can remove it from the hidden and showcase it in your profile.


🏁 Wrap up

Give yourself a pat on your back if you’ve reached this far, there are a few challenges ahead that I would like you to take up on

  1. After the art is generated give the user option to input a name, description, and address.

  2. Add loader screens when art is being generated.

  3. Add error screens if the art is not generated due to model being loaded.

  4. Give a minted confirmation

You can try out the live version here → https://ai-gasless-mint.vercel.app/

It should look something like this

https://i.imgur.com/2Ye5tRn.png

https://i.imgur.com/TBIJUv6.png

https://i.imgur.com/Bu0qWil.png

If you’ve tried this haven’t been succefull then feel free to checkout the Github repo for the solution → https://github.com/0xblocktrain/ai-art-gasless-mint

Also if you’ve reach this far do tag us on twitter and let us know the amazing work you’ve done!

Have fun building and shipping!

Â