Build an AI NFT minting app
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.
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
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.
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
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
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
Now click on Access token on the left sidebar and generate a new token
Now copy the token and then open your react codebase on any editor you like, Iâll go ahead with visual studio code.
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)
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
Create an input box in the middle and next button beside it.
Create a state that will store the input text.
Write a function to generate the ai image by calling hugging face api
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
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
The model ID is on the models page â https://huggingface.co/runwayml/stable-diffusion-v1-5
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
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.
Once you are logged in, click on the API keys tab on the top
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
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.
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
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
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
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
Now head over to opensea.io and connect your wallet and go to your profile and then hidden tab
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
After the art is generated give the user option to input a name, description, and address.
Add loader screens when art is being generated.
Add error screens if the art is not generated due to model being loaded.
Give a minted confirmation
You can try out the live version here â https://ai-gasless-mint.vercel.app/
It should look something like this
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!