Build an AI NFT minting app
Published: 2023-09-14 12:36:02

## **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](https://huggingface.co/) to use the stable diffusion model and generate the art via an API and mint this generated art on the blockchain using the [NFTport](https://nftport.xyz/) 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](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.

```bash
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](https://tailwindcss.com/)

```bash
cd ai-art-gasless-mint
```

For installing tailwind you can follow this step-by-step guide given by tailwind to install it → [Guide](https://tailwindcss.com/docs/guides/create-react-app)

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

![https://i.imgur.com/v4scFBf.png](https://i.imgur.com/v4scFBf.png align="left")

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](https://i.imgur.com/aVSPN6Q.png align="left")

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](https://i.imgur.com/4eLSIqb.png align="left")

Go over to [Huggingface/join](https://huggingface.co/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](https://i.imgur.com/OHoR1SO.png align="left")

We’ll be using [https://huggingface.co/runwayml/stable-diffusion-v1-5](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](https://i.imgur.com/21KDcpO.png align="left")

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](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](https://i.imgur.com/PFxWJ38.png align="left")

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

![https://i.imgur.com/xpbTGfD.png](https://i.imgur.com/xpbTGfD.png align="left")

![https://i.imgur.com/hRPNNan.png](https://i.imgur.com/hRPNNan.png align="left")

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](https://i.imgur.com/sJ0mgIH.png align="left")

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](https://i.imgur.com/xejo5A5.png align="left")

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

```bash
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.

```bash
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](https://i.imgur.com/FkITt2O.png align="left")

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://huggingface.co/docs/api-inference/quicktour)

![https://i.imgur.com/NS72LU3.png](https://i.imgur.com/NS72LU3.png align="left")

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

![https://i.imgur.com/mHQvE8E.png](https://i.imgur.com/mHQvE8E.png align="left")

This entire text is our model id.

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

```jsx
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

```jsx
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

```jsx
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.

```jsx
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](https://i.imgur.com/bLSRgNg.png align="left")

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](http://ipfs.tech/#how)

We’ll be using [NFT.storage](http://NFT.storage) for storing our files on IPFS.

Go over to [Nft.storage/login](http://Nft.storage/login) and sign in using either github or your email.

![https://i.imgur.com/NvN7lS5.png](https://i.imgur.com/NvN7lS5.png align="left")

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

![https://i.imgur.com/Cw4RgkS.png](https://i.imgur.com/Cw4RgkS.png align="left")

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](https://i.imgur.com/7Dd655w.png align="left")

Now go back to our terminal and install [nft.storage](http://nft.storage) package.

```bash
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](http://nft.storage) → [https://nft.storage/docs/#using-the-javascript-api](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.

```bash
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

```jsx
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](http://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](http://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.

```jsx
{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](https://i.imgur.com/oOhIIzi.png align="left")

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.

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

IPFS gateways are in the form

```jsx
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

```jsx
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](http://NFTport.xyz) and get our free API key first

![https://i.imgur.com/axAjFfv.png](https://i.imgur.com/axAjFfv.png align="left")

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](https://i.imgur.com/FR6u8UM.png align="left")

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://docs.nftport.xyz/reference/easy-minting-urls)

![https://i.imgur.com/Tuxzuxm.png](https://i.imgur.com/Tuxzuxm.png align="left")

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

```jsx
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](https://i.imgur.com/XXWIVvh.png align="left")

Now head over to [opensea.io](http://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/Mdl16ij.png align="left")

![https://i.imgur.com/jrDNovT.png](https://i.imgur.com/jrDNovT.png align="left")

![https://i.imgur.com/rAxFaTr.png](https://i.imgur.com/rAxFaTr.png align="left")

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/](https://ai-gasless-mint.vercel.app/)

It should look something like this

![https://i.imgur.com/2Ye5tRn.png](https://i.imgur.com/2Ye5tRn.png align="left")

![https://i.imgur.com/TBIJUv6.png](https://i.imgur.com/TBIJUv6.png align="left")

![https://i.imgur.com/Bu0qWil.png](https://i.imgur.com/Bu0qWil.png align="left")

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](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!