Build Your Own Full Stack NFT AI Art Minter On The Celo Blockchain

Build Your Own Full Stack NFT AI Art Minter On The Celo Blockchain https://celo.academy/uploads/default/optimized/2X/e/e0e325998c207101c47a217f2216f2a290386944_2_1024x576.png
none 0.0 0

Introduction

AI is a revolutionary technology that will transform our world as we know it. Its capabilities are endless and in this tutorial i’ll be showing you how to use an A.I model stablediffusion to generate high quality digital art and turn that art into an NFT.

Here’s a demo link of what you’ll be creating.

And a screenshot.

Prerequisites

To fully follow up with these tutorials, you should have a basic understanding of the following technologies.

Solidity, smart-contract and blockchain concepts.
Hardhat.
React.
Basic web Development.

Requirements

  • Solidity.
  • OpenZeppelin.
  • Hardhat.
  • React.
  • NodeJS 12.0.1 upwards installed.
  • Celo Extention wallet.

Installation

Click on this repo from your github.

  • Clone the repo to your computer.
  • open the project from from vscode.
  • Run npm install command to install all the dependencies required to run the app locally.

SmartContract

In this chapter, we’ll be creating our Nft minter contract

NFT Minter

Go to the contracts folder in root directory of the project and you will find the solidity file MyNFT.sol.

Open the file to file to access this smart contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract MyNFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    address public owner;

    constructor(
        string memory _name,
        string memory _symbol
    ) ERC721(_name, _symbol) {
        owner = msg.sender;
    }

    function mintArt(string memory tokenURI) public payable {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
    }

    function totalSupply() public view returns (uint256) {
        return _tokenIds.current();
    }

}

We create a smart contract that mints a new NFT (non-fungible token) using the ERC721 standard from the OpenZeppelin library. The contract allows anyone to mint a new NFT by calling the mintArt function and passing in a string parameter for the token URI. Each NFT has a unique ID that is incremented using a counter from the Counters library. The contract also keeps track of the total number of NFTs that have been minted using the totalSupply function. Additionally.

Front end

In this Part, we’ll be building a React application that will allow users to create and mint their own Non-Fungible Tokens (NFTs). We’ll be using the Celo Wallet as our wallet provider, the NFT.Storage api to upload a user’s custom image to IPFS, and the Hugging Face API to generate a custom image based on the user’s description.

Setup .env File

Firstly, we need to grap our environment variables as seen in the env example.

To get our hugging face api key, click here create an account and navigate to your profile and create a new api key.

You’ll also need to create an account on NFT.Storage, and create a new API key.

And finally we need to provide a mnemonic for our celo wallet.

Deployment

We’ll use hardhat to deploy our smart-contracts to the celo blockchain.

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config({ path: ".env" });

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

module.exports = {
  solidity: "0.8.9",
  networks: {
    alfajores: {
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0",
      },
      chainId: 44787,
    },
  },
};

Configure your hardhat.config file to look like this to enable hardhat to deploy the smart contracts to the celo blockchain.

Next let’s create a script to deploy the smart contracts.

const hre = require("hardhat");

async function main() {
  const NAME = "AI Generated NFT";
  const SYMBOL = "AINFT";

  const MyNFT = await hre.ethers.getContractFactory("MyNFT");
  const myNFT = await MyNFT.deploy(NAME, SYMBOL);

  await myNFT.deployed();

  console.log("MyNFT deployed to:", myNFT.address);
  storeContractData(myNFT);
}

function storeContractData(contract) {
  const fs = require("fs");
  const contractsDir = __dirname + "/../src/contracts";

  if (!fs.existsSync(contractsDir)) {
    fs.mkdirSync(contractsDir);
  }

  fs.writeFileSync(
    contractsDir + "/MyNFT-address.json",
    JSON.stringify({ MyNFT: contract.address }, undefined, 2)
  );

  const MyNFTArtifact = artifacts.readArtifactSync("MyNFT");

  fs.writeFileSync(
    contractsDir + "/MyNFT.json",
    JSON.stringify(MyNFTArtifact, null, 2)
  );
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

The Script Above will deploy our minter smart contracts and create a contract folder for the ABI and contract address for the NFT minter contract.

Deploy the smart contracts to the celo block-chain by running this command.

npx hardhat run scripts/deploy.js --network alfajores

You should see something like this in the terminal

MyNFT deployed to: 0x49F39D9531B826826EDc7066161F20570105AFb1

App.js

Now lets break down the main functionalities of our A.I Art Minter.
This is the compplete App.js code

import { NFTStorage, File } from "nft.storage";
import { Buffer } from "buffer";
import Web3 from "web3";
import { newKitFromWeb3 } from "@Celo_Academy/contractkit";
import { useState, useEffect, useCallback } from "react";

import axios from "axios";
import MyNFTAbi from "./contracts/MyNFT.json";
import MyNFTAddress from "./contracts/MyNFT-address.json";

// Components
import Spinner from "react-bootstrap/Spinner";
import Navigation from "./components/Navigation";

const ERC20_DECIMALS = 18;

function App() {
  const [contract, setContract] = useState(null);
  const [address, setAddress] = useState(null);
  const [kit, setKit] = useState(null);
  const [cUSDBalance, setcUSDBalance] = useState(0);

  // const [nft, setNFT] = useState(null)

  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [image, setImage] = useState(null);
  const [url, setURL] = useState(null);

  const [message, setMessage] = useState("");
  const [isWaiting, setIsWaiting] = useState(false);

  const connectToWallet = async () => {
    if (window.celo) {
      try {
        await window.celo.enable();
        const web3 = new Web3(window.celo);
        let kit = newKitFromWeb3(web3);

        const accounts = await kit.web3.eth.getAccounts();
        const user_address = accounts[0];
        kit.defaultAccount = user_address;

        await setAddress(user_address);
        await setKit(kit);
      } catch (error) {
        console.log(error);
      }
    } else {
      alert("Error Occurred");
    }
  };

  const getBalance = useCallback(async () => {
    try {
      const balance = await kit.getTotalBalance(address);
      const USDBalance = balance.cUSD.shiftedBy(-ERC20_DECIMALS).toFixed(2);

      const contract = new kit.web3.eth.Contract(
        MyNFTAbi.abi,
        MyNFTAddress.MyNFT
      );
      setContract(contract);
      setcUSDBalance(USDBalance);
    } catch (error) {
      console.log(error);
    }
  }, [address, kit]);

  const submitHandler = async (e) => {
    e.preventDefault();

    if (name === "" || description === "") {
      window.alert("Please provide a name and description");
      return;
    }

    setIsWaiting(true);

    // Call AI API to generate a image based on description
    const imageData = await createImage();

    // Upload image to IPFS (NFT.Storage)
    if (imageData) {
      const url = await uploadImage(imageData);
      await mintImage(url);
    }

    // Mint NFT

    setIsWaiting(false);
    setMessage("");
  };

  const createImage = async () => {
    setMessage("Generating Image...");

    // You can replace this with different model API's
    const URL = `https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-2`;

    // Send the request
    const response = await axios({
      url: URL,
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.REACT_APP_HUGGING_FACE_API_KEY}`,
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      data: JSON.stringify({
        inputs: description,
        options: { wait_for_model: true },
      }),
      responseType: "arraybuffer",
    });

    const type = response.headers["content-type"];
    const data = response.data;

    const base64data = Buffer.from(data).toString("base64");
    const img = `data:${type};base64,` + base64data; // <-- This is so we can render it on the page
    setImage(img);
    console.log(data);

    return data;
  };

  const uploadImage = async (imageData) => {
    setMessage("Uploading Image...");

    // Create instance to NFT.Storage
    const nftstorage = new NFTStorage({
      token: process.env.REACT_APP_NFT_STORAGE_API_KEY,
    });

    // Send request to store image
    const { ipnft } = await nftstorage.store({
      image: new File([imageData], "image.jpeg", { type: "image/jpeg" }),
      name: name,
      description: description,
    });

    // Save the URL
    const url = `https://ipfs.io/ipfs/${ipnft}/metadata.json`;
    setURL(url);

    return url;
  };

  const mintImage = async (tokenURI) => {
    setMessage("Waiting for Mint...");
    await contract.methods.mintArt(tokenURI).send({ from: address });
  };

  useEffect(() => {
    connectToWallet();
  }, []);

  useEffect(() => {
    if (kit && address) {
      getBalance();
    }
  }, [kit, address, getBalance]);

  return (
    <div>
      <Navigation
        connectToWallet={connectToWallet}
        address={address}
        balance={cUSDBalance}
      />

      <div className="form">
        <form onSubmit={submitHandler}>
          <input
            type="text"
            placeholder="Create a name..."
            onChange={(e) => {
              setName(e.target.value);
            }}
          />
          <input
            type="text"
            placeholder="Create a description..."
            onChange={(e) => {
              setDescription(e.target.value);
            }}
          />
          <input type="submit" value="Create & Mint" />
        </form>

        <div className="image">
          {!isWaiting && image ? (
            <img src={image} alt="AI generated image" />
          ) : isWaiting ? (
            <div className="image__placeholder">
              <Spinner animation="border" />
              <p>{message}</p>
            </div>
          ) : (
            <></>
          )}
        </div>
      </div>

      {!isWaiting && url && (
        <p>
          View&nbsp;
          <a href={url} target="_blank" rel="noreferrer">
            Metadata
          </a>
        </p>
      )}
    </div>
  );
}

export default App;

Breakdown

Let’s break down app.js file.

Let’s start by importing the necessary libraries and components into our App.js file.

import { NFTStorage, File } from "nft.storage";
import { Buffer } from "buffer";
import Web3 from "web3";
import { newKitFromWeb3 } from "@Celo_Academy/contractkit";
import { useState, useEffect, useCallback } from "react";

import axios from "axios";
import MyNFTAbi from "./contracts/MyNFT.json";
import MyNFTAddress from "./contracts/MyNFT-address.json";

// Components
import Spinner from "react-bootstrap/Spinner";
import Navigation from "./components/Navigation";

Next, we’ll define the constant ERC20_DECIMALS that we’ll be using in our application. Now, let’s create the state variables that we’ll be using in our application. We’ll be using the contract, address, kit, cUSDBalance, name, description, image, url, message and isWaiting variables to store the user’s wallet address, the contract instance, the user’s balance in cUSD, the name and description of the NFT, the image, the URL of the image, a message to be displayed to the user, and a boolean value to indicate whether the user is waiting for a transaction to be mined.

const ERC20_DECIMALS = 18;

function App() {

  const [contract, setContract] = useState(null);
  const [address, setAddress] = useState(null);
  const [kit, setKit] = useState(null);
  const [cUSDBalance, setcUSDBalance] = useState(0);

 // const [nft, setNFT] = useState(null)

  const [name, setName] = useState("")
  const [description, setDescription] = useState("")
  const [image, setImage] = useState(null)
  const [url, setURL] = useState(null)

  const [message, setMessage] = useState("")
  const [isWaiting, setIsWaiting] = useState(false)

Now that we have our state variables defined, let’s create the connectToWallet function, which will be used to connect the user’s wallet to our application. We’ll be using the Celo Wallet as our wallet provider, and the newKitFromWeb3 library to create a new instance of our wallet. Once the wallet is connected, we’ll store the user’s address in the address state variable, and the wallet instance in the kit state variable.

const connectToWallet = async () => {
  if (window.celo) {
    try {
      await window.celo.enable();
      const web3 = new Web3(window.celo);
      let kit = newKitFromWeb3(web3);

      const accounts = await kit.web3.eth.getAccounts();
      const user_address = accounts[0];
      kit.defaultAccount = user_address;

      await setAddress(user_address);
      await setKit(kit);
    } catch (error) {
      console.log(error);
    }
  } else {
    alert("Error Occurred");
  }
};

Now, let’s create the getBalance function, which will be used to get the user’s cUSD balance. This function will be called when the user connects their wallet, and will set the cUSDBalance state variable with the user’s balance and also initialize our contract object.

const getBalance = useCallback(async () => {
  try {
    const balance = await kit.getTotalBalance(address);
    const USDBalance = balance.cUSD.shiftedBy(-ERC20_DECIMALS).toFixed(2);

    const contract = new kit.web3.eth.Contract(
      MyNFTAbi.abi,
      MyNFTAddress.MyNFT
    );
    setContract(contract);
    setcUSDBalance(USDBalance);
  } catch (error) {
    console.log(error);
  }
}, [address, kit]);

Now that we have our connectivity functions defined, let’s create the submitHandler function, which will be used to handle the submission of the form. This function will be used to generate the image using the Hugging Face API, upload the image to IPFS using the NFT.Storage library, and mint the NFT using the user’s wallet.

const submitHandler = async (e) => {
  e.preventDefault();

  if (name === "" || description === "") {
    window.alert("Please provide a name and description");
    return;
  }

  setIsWaiting(true);

  // Call AI API to generate a image based on description
  const imageData = await createImage();

  // Upload image to IPFS (NFT.Storage)
  if (imageData) {
    const url = await uploadImage(imageData);

    // Mint an Image
    await mintImage(url);
  }

  setIsWaiting(false);
  setMessage("");
};

Next, let’s create the createImage function, which will be used to generate the image based on the user’s description. We’ll be using the Hugging Face API for this, and we’ll store the image in the image state variable.

const createImage = async () => {
  setMessage("Generating Image...");

  // You can replace this with different model API's
  const URL = `https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-2`;

  // Send the request
  const response = await axios({
    url: URL,
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.REACT_APP_HUGGING_FACE_API_KEY}`,
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    data: JSON.stringify({
      inputs: description,
      options: { wait_for_model: true },
    }),
    responseType: "arraybuffer",
  });

  const type = response.headers["content-type"];
  const data = response.data;

  const base64data = Buffer.from(data).toString("base64");
  const img = `data:${type};base64,` + base64data; // <-- This is so we can render it on the page
  setImage(img);
  console.log(data);

  return data;
};

Next, let’s create the uploadImage function, which will be used to upload the image to IPFS using the NFT.Storage library. We’ll store the URL of the image in the url state variable.

const uploadImage = async (imageData) => {
  setMessage("Uploading Image...");

  // Create instance to NFT.Storage
  const nftstorage = new NFTStorage({
    token: process.env.REACT_APP_NFT_STORAGE_API_KEY,
  });

  // Send request to store image
  const { ipnft } = await nftstorage.store({
    image: new File([imageData], "image.jpeg", { type: "image/jpeg" }),
    name: name,
    description: description,
  });

  // Save the URL
  const url = `https://ipfs.io/ipfs/${ipnft}/metadata.json`;
  setURL(url);

  return url;
};

Finally, let’s create the mintImage function, which will be used to mint the NFT. This function will be called after the image has been uploaded to IPFS, and will use the ContractKit library to mint the NFT.

const mintImage = async (tokenURI) => {
  setMessage("Waiting for Mint...");
  await contract.methods.mintArt(tokenURI).send({ from: address });
};

We’ll use the useEffect hook to connect to the Celo wallet and get the balance when the kit and address are set.

useEffect(() => {
  connectToWallet();
}, []);

useEffect(() => {
  if (kit && address) {
    getBalance();
  }
}, [kit, address, getBalance]);

Next. let’s render our components which will displays a form for creating and minting NFT Digital Arts The form includes input fields for name and description and a button to submit the form. After submitting the form, a loading spinner is displayed while the image for the NFT is generated. Once the image is generated and minted, it is displayed, and a link to view the metadata for the NFT is provided. The component also displays the user’s wallet address and balance.

 return (
    <div>
      <Navigation connectToWallet={connectToWallet} address={address} balance={cUSDBalance} />

      <div className='form'>
        <form onSubmit={submitHandler}>
          <input type="text" placeholder="Create a name..." onChange={(e) => { setName(e.target.value) }} />
          <input type="text" placeholder="Create a description..." onChange={(e) => { setDescription(e.target.value)}} />
          <input type="submit" value="Create & Mint" />
        </form>

        <div className="image">
          {!isWaiting && image ? (
            <img src={image} alt="AI generated image" />
          ) : isWaiting ? (
            <div className="image__placeholder">
              <Spinner animation="border" />
              <p>{message}</p>
            </div>
          ) : (
            <></>
          )}
        </div>
      </div>

      {!isWaiting && url && (
        <p>
          View&nbsp;<a href={url} target="_blank" rel="noreferrer">Metadata</a>
        </p>
      )}
    </div>
  );
}

export default App;

Next Steps

I hope you learned a lot from this tutorial. Here are some relevant links that would aid your learning further.

About the author

I’m Jonathan Iheme, A full stack block-chain Developer from nigeria who is passionate abot block-chain technology.

Thank You!!

3 Likes

The content of the tutorial is highly informative and well-structured. As itt offers a comprehensive guide for developers interested in building a full-stack NFT AI art minter on the Celo blockchain. The tutorial covers all the essential aspects, including smart contract development, front-end implementation, and integration with external services. Nice one boss​:+1::heart_eyes:

1 Like

Wow, To all sages, kindly empower your creativity and blockchain skills with this comprehensive tutorial on building a full stack NFT AI art minter on the Celo blockchain. Unlock the world of decentralized art and showcase your unique creations with this step-by-step guide.