DEV Community

Cover image for How to Create a FullStack Dapp using Next JS, Tailwind CSS and The graph. (Part 1)
Adebayo Olamilekan
Adebayo Olamilekan

Posted on • Edited on

How to Create a FullStack Dapp using Next JS, Tailwind CSS and The graph. (Part 1)

Hello there πŸ–πŸ½, This is a written tutorial on a Fullstack Decentralized application using some web3 technologies, I call this dapp NFT AIR

The idea behind this dapp is to allow people to showcase their artwork to others, but not for sale. it can be memes, pictures, drawings, sketches etc.

And I know what you're thinking and NO its not like an Opensea type of Dapp or a web3 version of insta, and yeah its a bit similar to those apps as people can upload their artwork for viewing, which can either be downloadable or not also seeing and also able to react to posts of others.

The full code of we'll be going through in this tutorial is available on my GitHub here you can check it as we progress down the tutorial, the fully deployed website is also here.

[NOTE : If you wan to improve some feature you are welcome to make Pull requests or raise an issue ill be sure o look at them and work on it together]

The preview of NFT AIR's website
Nft Air's website preview

The preview of the Dapp (above) is what this tutorial is gonna achieve and likei said before the name i gave it is NFT AIR (Don't ask about it, I know it sucks πŸ™ˆ).
Unfortunately this is only Part 1 of the full Dapp so this Part only explains and shows how the backend works using theses technologies :

  • Solidity: The language used to write the backend of the Dapp.
  • Hardhat: This is the Ethereum development environment we'll be using in this tutorial.
  • The graph protocol: This is used for indexing our data that will be uploaded to the blockchain.
  • Ankr: this is our rpc node provider
  • Polygon Mumbai Testnet: The Blockchain we'll be using in this tutorial.
  • Ethers.js: Ethereum Web Client Library used to interact with the blockchain.

Challenges and Thought

I didn't face much challenges when building the backend as I've done something similar in a previous project, but this is my first time using The graph protocol and reading about and using Ankr node provider for a Dapp project I mostly use Alchemy or Infura.

Overview Of The Project

Let's take a look at the backend of this NFT AIR project in a simpler manner, If so then these are what we'll be accomplishing in this Part

  1. Installing dependencies
  2. Writing the Smart contract in Solidity language
  3. Testing the Smart contract and Hardhat Config.
  4. Deploying of Smart Contract
  5. Setting up a subgraph.
  6. Tracking and storing our data emitted using The graph Indexers

Prerequisites

  1. Text Editor of your choice Installed on your machine (preferably Vs code )
  2. Node.js should be installed also.
  3. And most importantly have a nice bottle of water/juice next to you πŸ˜‰.

Project BuidLing and Setup

Lets Begin! (Take a sip of that drink you have and lest start)

  • Next app In your Command prompt/terminal pointing to the location you want the dapp in, and then you run the code below using npm or yarn or pnpm, any one of your choice to create a next app
yarn create next-app nft-air-dapp-tutorial
Enter fullscreen mode Exit fullscreen mode
  • Installing dependencies we need to install a few dependencies before writing any line of code, we would we installing; hardhat, ether.js. ( Note : in the "nft-air-dapp-tutorial" path )
  yarn add ethers hardhat @nomiclabs/hardhat-waffle ethereum- 
  waffle chai @nomiclabs/hardhat-ethers @openzeppelin/contracts 
  dotenv
Enter fullscreen mode Exit fullscreen mode

you can also use "npm install" in place of yarn add

  • Hardhat Development Environment

In this tutorial we'll be using hardhat as the Ethereum dev environment, there are others like foundry , Truffle.

yarn hardhat
Enter fullscreen mode Exit fullscreen mode

Hardhat Terminal

After running the line above you should be asked some question on the path and type of project, etc . Do them correctly and then open your app in vs code using the code below in your terminal.

code .
Enter fullscreen mode Exit fullscreen mode

then your Visual Studio window should open up with some files and folders in the "Explorer tab" should look like this

File structure

The above image should be similar to your file structure in your Text editor.

  • contracts: This folder would contain our smart contracts files written in solidity.

  • node_modules : This folder contains the dependencies installed that would be used in the project.

  • pages: This folder is for the frontend part where the different pages of our dapp would be kept in.

  • public: This is also a frontend folder where our media files would be kept.

  • scripts: This folder keeps in the deploy script for our contract.

  • styles: Like the name contains Files related to the styling for the frontend aspect of this dapp.

  • test: This folder as the name suggests contains test scripts written in JavaScript to test our smart contract before deployment.

Configuration

In your hardhat.config.js file located in your project's root directory you can paste the code below there , (I used solidity version 0.8.7 here yours may be higher)

require("@nomiclabs/hardhat-waffle");

module.exports = {
  solidity: "0.8.7",
  networks: {
    hardhat:{
      chainId:1337
    },
};
Enter fullscreen mode Exit fullscreen mode

This sets the solidity version used and the local hardhat network.

Smart Contract

The smart contract code for this project is a bit much and may be overwhelming at first look, but I will explain through all the functions.
The full solidity code for the contracts is here.
To start create a new file in your contracts folder and name it Nft-Air.sol and paste this code into it.

(If you are having errors showing up on your import lines install Solidity and hardhat extension in vs code )

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

import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MemeForest is ReentrancyGuard{
    using Counters for Counters.Counter;
    Counters.Counter public NumOfAllMemes;
    Counters.Counter public NumOfAllMembers;

    struct MemeMembers {
        string Name;
        address MemeberAddress;
        uint MyId;
        uint MyMemes;
        uint MyStarredMemes;
        uint MyDeletedMemes;
        string Datejoined;
    }

    struct MemeFiles {
        string Memeinfo;
        address Owner;
        uint fileId;
        bool starred;
        uint Stars;
        uint Likes;
        string DateOfCreation;
        string FileType;
        bool IsDownloadable;
    }

    mapping(uint => MemeMembers) private IdMembers;
    mapping(address => bool) private alreadyAMember;
    mapping(address => mapping(uint => bool )) private DidyouStar;
    mapping(address => mapping(uint => bool )) private DidyouLike;
    mapping (uint => MemeFiles) private IdMemeFiles;
    mapping(uint => address) private StarredMemeFiles;

    uint public NumberOfUploads;

    event Memberjoined (
        uint256 MemberId,
        string MemberName,
        string Datejoined,
        address MemberAddress, 
        uint256 MemberTotalMemes,
        uint256 MemberStarredMemes,
        uint256 MemberDeletedMemes,
        uint256 MemberTotalLikes
    );

    event CreateMeme (
        uint256 MemeId,
        string MemeInfo,
        address MemeCreator,
        bool IsMemeStarred,
        uint256 MemeStars,
        uint256 MemeLikes,
        string DateOfCreation,
        string Filetype,
        bool IsDownloadable,
        uint Membernum,
        uint NewNumberMemberMemes
    );

    event StarredMeme (
        uint256 MemeId,
        uint256 NewStarNo,
        uint256 CreatorId,
        address CreatorAddress,
        uint256 CreatorStarredMemes
    );

    event UnStarringMeme (
        uint256 MemeId,
        uint256 NewStarNo,
        uint256 CreatorId,
        address CreatorAddress,
        uint256 CreatorStarredMemes
    );
    event LikingMeme (
        uint256 MemeId,
        uint256 NewLikesNo,
        uint256 CreatorId,
        address liker
    );
    event UnLikingMeme ( 
       uint256 MemeId,
        uint256 NewLikesNo,
        uint256 CreatorId,
        address Unliker
        );

//////////////////////////////
/////////Functions here//////
/////////////////////////////

}
Enter fullscreen mode Exit fullscreen mode

Explaining the full contract in parts this above contains:

  • The libraries imported,
  • Global variables used in the contract,
  • mappings, structs and events

This part of the smart contract doesn't have any functions only variables.

In the above contract after importing a couple of libraries from openzeppelin and also for writing to the console using console.sol from hardhat.

There are two structs are defined which represent the details for both members of the dapp and their artwork (nft created).
Which is followed by a series of mappings that are used to keep track of some variables.

Finally there are 6 different events created which represent the actions that would affect/change the data stored on the blockchain, they are going to be emitted later on which signals the subgraph.

The remaining smart contract code (below)

function CreateMembers (string memory _name, string memory _date) public nonReentrant{
        require(alreadyAMember[msg.sender] == false, "You are already a member");

        NumOfAllMembers.increment();
        uint currentMemberId = NumOfAllMembers.current();

        IdMembers[currentMemberId] = MemeMembers (
            _name,
            msg.sender,
            currentMemberId,
            0,
            0,
            0,
            _date

        );

        alreadyAMember[msg.sender] = true;

        emit Memberjoined (
            currentMemberId,
            _name,
            _date,
            msg.sender,
            0,
            0,
            0,
            0
        );
    }


    function fetchMembers() public view returns(MemeMembers[] memory) {
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentIndex = 0;
        MemeMembers[] memory members = new MemeMembers[] (currentMemberNum);
        for (uint256 index = 0; index < currentMemberNum; index++) {
            uint currenNum = IdMembers[index + 1].MyId;
            MemeMembers storage memeMem = IdMembers[currenNum];
            members[currentIndex] = memeMem;
            currentIndex+=1;
        }
        return members;
    }


    function GetMemberByAddr(address _member)external view returns(MemeMembers[] memory){
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentIndex = 0;
        MemeMembers[] memory foundMember = new MemeMembers[] (1);
        for(uint i = 0; i< currentMemberNum; i++){
            if(_member == IdMembers[i+1].MemeberAddress ){
                uint currentmem = IdMembers[i+1].MyId;
                MemeMembers storage memMem = IdMembers[currentmem];
                foundMember[currentIndex] = memMem;
            }
        }
        return foundMember;
    }


    function IsAMember(address sender) external view returns(bool) {
        bool member = alreadyAMember[sender];
        return member;
    }

    function CreateMemeItems( string memory memeinfo,
    address _owner, 
    string memory _date,
    string memory _filetype,
    bool _isDownloadable
    ) 
    public nonReentrant{
        NumOfAllMemes.increment();
        uint256 currentMeme =  NumOfAllMemes.current();
        IdMemeFiles[currentMeme] = MemeFiles(
            memeinfo,
            _owner,
            currentMeme,
            false,
            0,
            0,
            _date,
            _filetype,
            _isDownloadable
        );
         uint currentMemberNum = NumOfAllMembers.current();
          uint currentNum;
          uint newMemes;
        for (uint i = 0; i < currentMemberNum; i++) {
            if(_owner == IdMembers[i+1].MemeberAddress){
                currentNum = IdMembers[i+1].MyId;
                newMemes = IdMembers[currentNum].MyMemes;
                newMemes +=1;
                IdMembers[currentNum].MyMemes = newMemes;
            }
        }

        emit CreateMeme (
            currentMeme,
            memeinfo,
            _owner,
            false,
            0,
            0,
            _date,
            _filetype,
            _isDownloadable,
            currentNum,
            newMemes
        );
    }


    function fetchAllMemes() public view returns(MemeFiles[] memory) {

        uint currentMemeNum = NumOfAllMemes.current();

        uint currentIndex = currentMemeNum;
        MemeFiles[] memory memes = new MemeFiles[] (currentMemeNum);

        for (uint256 index = 0; index < currentMemeNum; index++) {
            uint currenNum = IdMemeFiles[index +1].fileId;
            MemeFiles storage memeFiles = IdMemeFiles[currenNum];

            memes[currentIndex - 1] = memeFiles;
            currentIndex-=1;
        }
        return memes;
    }

    function LikeMeme(uint _id) public {
        uint currentMemeNum = NumOfAllMemes.current();
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        uint256 newLikes;
        for(uint i = 0; i < currentMemeNum; i++){
            if(_id == IdMemeFiles[i+1].fileId) {

                newLikes = IdMemeFiles[i+1].Likes;
                newLikes +=1;
                IdMemeFiles[i+1].Likes =  newLikes;
                DidyouLike[msg.sender][_id]= true;  
            }
        }
        for (uint index = 0; index < currentMemberNum; index++) {
            if(msg.sender == IdMembers[index+1].MemeberAddress){
                currentNum = IdMembers[index+1].MyId;
            }
        }

        emit LikingMeme(
            _id,
            newLikes,
            currentNum,
            msg.sender
        );
    }

    function UnLikeMeme(uint _id) public {
        uint currentMemeNum = NumOfAllMemes.current();
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        uint256 newLikes; 
        for(uint i = 0; i < currentMemeNum; i++){
            if(_id == IdMemeFiles[i+1].fileId) {

               newLikes = IdMemeFiles[i+1].Likes;
                newLikes -=1;
                IdMemeFiles[i+1].Likes =  newLikes;
                DidyouLike[msg.sender][_id]= false;
            }
        }
        for (uint index = 0; index < currentMemberNum; index++) {
            if(msg.sender == IdMembers[index+1].MemeberAddress){
                currentNum = IdMembers[index+1].MyId;
            }
        }

        emit UnLikingMeme(
            _id,
            newLikes,
            currentNum,
            msg.sender
        );
    }

    function WhatDidILike (uint _id, address sender) public view returns (bool) {
         bool youLiked =  DidyouLike[sender][_id];
         return youLiked;
    }

    function StarMeme(uint _id ) public {
        uint currentMemeNum = NumOfAllMemes.current();
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        uint newstars;
        uint newstarredMemes;
        for(uint i = 0; i < currentMemeNum; i++){
            if(_id == IdMemeFiles[i+1].fileId) {
                IdMemeFiles[_id].starred = true;
                newstars=IdMemeFiles[_id].Stars;
                newstars+=1;
                IdMemeFiles[_id].Stars = newstars ;
                DidyouStar[msg.sender][_id]= true; 
            }
        }
        for (uint index = 0; index < currentMemberNum; index++) {
            if(msg.sender == IdMembers[index+1].MemeberAddress){
                currentNum = IdMembers[index+1].MyId;
                newstarredMemes=IdMembers[currentNum].MyStarredMemes;
                newstarredMemes +=1;
                IdMembers[currentNum].MyStarredMemes = newstarredMemes;
            }
        }
        emit StarredMeme (
        _id,
        newstars, 
        currentNum,
        msg.sender,
        newstarredMemes
        );
    }


    function RemoveStarMeme(uint _id) public {
        uint currentMemeNum = NumOfAllMemes.current();
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        uint newstars;
        uint newstarredMemes;
        for(uint i = 0; i < currentMemeNum; i++){
            if(_id == IdMemeFiles[i+1].fileId) {
                IdMemeFiles[_id].starred = false;
                newstars=IdMemeFiles[_id].Stars;
                newstars-=1;
                IdMemeFiles[_id].Stars = newstars ;
                DidyouStar[msg.sender][_id]= false;
            }
        }
         for (uint index = 0; index < currentMemberNum; index++) {
            if(msg.sender == IdMembers[index+1].MemeberAddress){
                 currentNum = IdMembers[index+1].MyId;
                newstarredMemes=IdMembers[currentNum].MyStarredMemes;
                newstarredMemes -=1;
                IdMembers[currentNum].MyStarredMemes = newstarredMemes;
             }
        }
        emit UnStarringMeme (
         _id,
        newstars, 
        currentNum,
        msg.sender,
        newstarredMemes
        );
    }

    function WhatDidIStar (uint _id, address sender) public view returns (bool) {
         bool youStarred =  DidyouStar[sender][_id];
         return youStarred;
    }

    function fetchMyStarredMemes(address sender) public view returns (MemeFiles[] memory) {
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        for (uint i = 0; i < currentMemberNum; i++) {
            if(sender == IdMembers[i+1].MemeberAddress){
                uint val = IdMembers[i+1].MyId;
                currentNum = IdMembers[val].MyStarredMemes;
            }
        }

        uint currentMemeNum = NumOfAllMemes.current();
        MemeFiles[] memory memes  = new MemeFiles[] (currentNum);
        uint currentIndex = 0;
        for (uint index = 0; index < currentMemeNum; index++) {
            uint id = IdMemeFiles[index+1].fileId;
            if(DidyouStar[sender][id] == true && IdMemeFiles[id].starred == true ){   
                MemeFiles storage memeFiles = IdMemeFiles[id];
                memes[currentIndex] = memeFiles;
                currentIndex+=1;
            }
        }   
        return memes; 
    }

    function fetchMyMeme(address sender) public view returns (MemeFiles[] memory) {
        uint currentMemberNum = NumOfAllMembers.current();
        uint currentNum;
        for (uint i = 0; i < currentMemberNum; i++) {
            if(sender == IdMembers[i+1].MemeberAddress){
                uint val = IdMembers[i+1].MyId;
                currentNum = IdMembers[val].MyMemes;
                console.log(val);
            }
        }

        uint currentMemeNum = NumOfAllMemes.current();
        uint currentIndex = 0;
        MemeFiles[] memory memes = new MemeFiles[] (currentNum);
         for (uint i = 0; i < currentMemeNum; i++) {
            uint id = IdMemeFiles[i+1].fileId;
            if(sender ==  IdMemeFiles[id].Owner  ){ 
                MemeFiles storage memeFiles = IdMemeFiles[id];
                memes[currentIndex] = memeFiles;
                currentIndex+=1;
            }
         }
         return memes;
    }

Enter fullscreen mode Exit fullscreen mode

There might be a confusion due to the word 'memes' used a lot this is because this dapp is an upgrade of this called Memeforest.

The functions above are to be placed in the specified area in the first half of the smart contract.

The functions above are a self explanatory as their names represent what they do.

Testing the smart contract

We have to test our smart contract before deployment to find errors that needs fixing, (All errors need fixing though lol xD ).

In order to do this open your test folder in your project's root directory and open the file named smaple-text.js and paste the code below inside (This is where we write our tests in js)

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Greeter", function () {
  it("Should create two members,ceate two nft,like one,star one,fetch all, fetch starred and fetch mine " , async function () {
    const Nft = await ethers.getContractFactory("MemeForest");
    const nft= await Nft .deploy();
    await nft.deployed();

    let today = new Date().toISOString().slice(0, 10);
    console.log(today)
    const [_, buyerAddress,thirdone] = await ethers.getSigners()
    const createMember = await nft.connect(buyerAddress).CreateMembers("first kid", today);
    const createMember2 = await nft.connect(thirdone).CreateMembers("second kid", today);
    const fetchMembers= await nft.connect(buyerAddress).fetchMembers();
    console.log(fetchMembers);
    const addr =  await buyerAddress.getAddress()
    const addr2 =  await thirdone.getAddress()
    const fectme = await nft.GetMemberByAddr(addr);
    console.log(fectme);
    let another = new Date().toISOString().slice(0, 10);
    await nft.connect(buyerAddress).CreateMemeItems("MemeLinkInfo1",addr,another,"jpeg",true);
    await nft.connect(thirdone).CreateMemeItems("MemeLinkInfo2",addr2,another,"mp4",false);

    const Allmeme = await nft.fetchAllMemes()
    console.log(Allmeme)

    console.log("liking meme")
    await nft.connect(thirdone).LikeMeme("1");

    console.log("staring meme")
    await nft.connect(buyerAddress).StarMeme("2");


    console.log("fetching starred memes right now...............")
    const FetchStarredMemes = await nft.connect(buyerAddress).fetchMyStarredMemes(addr);

    console.log(FetchStarredMemes)
    console.log("fetching starred memes right now...............")

    console.log("fetching my meme")
    const first = await nft.connect(buyerAddress).fetchMyMeme(addr)
    console.log(first)
    console.log("fetching my second meme")
    const second = await nft.connect(thirdone).fetchMyMeme(addr2)
    console.log(second)

  });

});

Enter fullscreen mode Exit fullscreen mode

To run the above code do run the code below

yarn hardhat test

or

npx hardhat test
Enter fullscreen mode Exit fullscreen mode

You should get this message after a series of logs that appears on the terminal => βœ” Should create two members,ceate two nft,like one,star one,fetch all, fetch starred and fetch mine

After getting the successful message we can now deploy our contract and then the setup of our subgraph.

Deployment

The code below deploys our smart contract as well as writing the contact address of our contract to a file that we'll create now called constant.js in the root directory of our project

const hre = require("hardhat");
const filesys = require("fs");

async function main() {


  const Meme = await ethers.getContractFactory("MemeForest");
  const meme = await Meme.deploy();
  await meme.deployed();
  console.log("MemeForest deployed to:", meme.address);

  filesys.writeFileSync('./constant.js' , `
  export const MemeForestAddress ="${meme.address}"


  `)
}


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

Enter fullscreen mode Exit fullscreen mode

A new file called constant.js would be created in the root of the project, but before that we need to actually deploy our contract and to do that, we need to first update our
hardhat.config.js file as we will be using an rpc endpoint from ankr and also using our private key.

*.env file *

  • Create a file named '.env' in the base of project (i.e not inside any folder) then paste this code inside
ANKR_ID = ""
PRIVATE_KEY=" "
Enter fullscreen mode Exit fullscreen mode

We will need to populate the empty quotes, for the ANKR_ID visit ankr and get the mumbai testnet rpc endpoint here.

Ankr Homepage

Mumbai Rpc endpoint

Copy the above endpoint and paste in the .env file.

For the Private key you need to have Metamask installed in your browser.

Getting private key from metamask

  • First open your account details
  • The click on the Export private key button
  • Enter your Metamask password and then copy your private key.

Copy your private key

*NOTE: DO NOT SHARE YOUR PRIVATE KEY WITH ANYONE *

Paste the key in the PRIVATE_KEY variable in your .env file.

hardhat.config.js file should be updated to the code below

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

const ANKR_ID = process.env.ANKR_ID;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

module.exports = {
  solidity: "0.8.7",
  networks: {
    hardhat:{
      chainId:1337
    },
    mumbai:{
      url:ANKR_ID,
      accounts:[PRIVATE_KEY],
    },
  },


};
Enter fullscreen mode Exit fullscreen mode

So this is all we need to deploy our smart contract. Then we can finally run this

npx hardhat compile
npx hardhat run scripts/deploy.js --network mumbai

or 

yarn hardhat compile
yarn hardhat run scripts/deploy.js --network mumbai
Enter fullscreen mode Exit fullscreen mode

This deploys your smart contract and populate the constant.js file with your contract address.

Subgraph Setup

The graph protocol is a decentralized query protocol for indexing the blockchain. To understand more about the graph I suggest you read the docs.

Before we continue I hope you have yarn installed on your machine, if not get yarn here.

A brief explanation on how the graph protocol works; In our smart contract when we add/change data on the blockchain we emit events containing that information and it gets stored in the grph's node and when we need it back we can query the data just like how the picture below explains (from their website).

The Graph workings Illustration

To get started you'll need to create your own subgraph by heading to The graph hosted service, where you'll create an account if you don't have one and go to your dashboard, then click on Add Subgraph button.

My Dashoard
When creating the subgraph you will fill in some details about the subgraph for the dapp you are building.

Creating a subgraph

After filling the details about the subgraph then you are taken to page with the next steps and commands to follow shows up like in the diagram below.

Commands to follow

  • Firstly we'll need to run this (either yarn or npm) to install some packages needed.

picture

  • After the installation of the packages you need to create a file named abi.json in the root of your project, then go to the contracts subfolder in the artifacts folder, where you'll see another folder in the name of your contract, open it and open the file with this format YOUR-CONTRACT-NAME.json.

[artifacts/contracts/YOUR-CONTRACT-NAME.json]

Then copy the abi (starting with a square braces and ending with one then paste the copied code in the abi.json file you created).

Abi file

Then continue to follow the commands in the Init step .

Init

graph init --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME>

Enter fullscreen mode Exit fullscreen mode

Run the above, changing the "GITHUB_USER" to your GitHub username and the "SUBGRAPH NAME" to the name you gave this subgraph.

Choose Ethereum

Continue to follow the command, (remember the network is mumbai)

Follow

Write in abi.json where it asks for the file path, follow the rest and it generates a Subgraph.

Next steps

Then follow the next steps given like above.

NEXT

The deploy key is the "Access Key" with a combination of numbers and letters in your subgraph page.

Then run

yarn deploy
Enter fullscreen mode Exit fullscreen mode

pointing to your new subgraph folder.

A link should now be there going to your new deployed subgraph

Alas!!!!!!! We've deployed our Subgraph.

Its been a while so I suggest you take a sip from that drink of yours

Now then,

How your subgraph should look when expanded

folder

Open the build folder and go to "subgraph.yaml" file and change this
picture
by adding startBlock: BLOCK_NUMBER after the abi line.

Your block Number can be gotten by pasting your address in the MumbaiPolygonScan then scroll down to when the contract was created and copy your block number, like the below number in circle.

the block number

Done? Good work.

Open the schema.graphl above the sugbraph.yaml file and paste the code in there.

type Meme @entity {
  id: ID!
  MemeInfo: String! # string
  Owner: Bytes!
  IsStarred: Boolean!
  Stars: BigInt!
  Likes: BigInt!
  Date: String!
  FileType: String!
  IsDownloadable: Boolean!
  StarredAddresses:[Bytes!]
  LikesAddresses:[Bytes!]
}

type Memeber @entity {
  id: ID!
  Name: String!
  Adddress:Bytes!
  TotalMeme: BigInt!
  StarredMemes: BigInt!
  DeletedMemes: BigInt!
  Date: String!
}

Enter fullscreen mode Exit fullscreen mode

[Like I said before you might be confused with the word memes I use mostly, this is because nft air is an extension of Memeforest and you can change the meme words to nfts or anything you want.]

The schema above contains Entities that represent data structures that will be stored in the graph's node.

We have two entities due to the fact that we are only storing

  • Member details and
  • Their nft/memes/art they upload.

Run

yarn codegen
Enter fullscreen mode Exit fullscreen mode

To generate some code file, after which paste the code below in the typescript (.ts) file inside the src folder in your subgraph

import { Address, BigInt } from "@graphprotocol/graph-ts"
import {
  MemeForest,
  CreateMeme,
  LikingMeme,
  Memberjoined,
  StarredMeme,
  UnLikingMeme,
  UnStarringMeme
} from "../generated/MemeForest/MemeForest"
import { Meme, Memeber } from "../generated/schema"

export function handleCreateMeme(event: CreateMeme): void {
  // Entities can be loaded from the store using a string ID; this ID
  // needs to be unique across all entities of the same type
  let entity = Meme.load(event.params.MemeId.toString())
  // let address = new Address (0)
  // Entities only exist after they have been saved to the store;
  // `null` checks allow to create entities on demand
  if (!entity) {
    entity = new Meme(event.params.MemeId.toString())
  }
  entity.MemeInfo = event.params.MemeInfo;
  entity.Owner= event.params.MemeCreator;
  entity.IsStarred = event.params.IsMemeStarred;
  entity.Stars = event.params.MemeStars;
  entity.Likes = event.params.MemeLikes;
  entity.Date = event.params.DateOfCreation;
  entity.FileType = event.params.Filetype;
  entity.IsDownloadable = event.params.IsDownloadable;
  entity.StarredAddresses =[]
  entity.LikesAddresses =[]



  let member = Memeber.load(event.params.Membernum.toString());
  if(!member) {
    return ;
  }
  member.TotalMeme = event.params.NewNumberMemberMemes;

  member.save()
  entity.save()

}

export function handleLikingMeme(event: LikingMeme): void {
  let entity = Meme.load(event.params.MemeId.toString())

  if(!entity) {
    return ;
  }

  entity.Likes = event.params.NewLikesNo;

  let member = Memeber.load(event.params.CreatorId.toString());
  // You have to be a member to be able to like a meme
  if(!member){
    return;
  }
  let Thisaddress = member.Adddress;
  let memes = entity.LikesAddresses
  if(!memes){
    return;
  }
  memes.push(Thisaddress);
  entity.LikesAddresses = memes;
  entity.save()
}

export function handleMemberjoined(event: Memberjoined): void {
  let member = Memeber.load(event.params.MemberId.toString());
  if(!member) {
    member = new  Memeber(event.params.MemberId.toString());

  }
    member.Adddress = event.params.MemberAddress;
    member.Name = event.params.MemberName;
    member.Date = event.params.Datejoined;
    member.TotalMeme = event.params.MemberTotalMemes;
    member.StarredMemes = event.params.MemberStarredMemes;
    member.DeletedMemes = event.params.MemberDeletedMemes;
    member.save()
}

export function handleStarredMeme(event: StarredMeme): void {
  let entity = Meme.load(event.params.MemeId.toString())
  if(!entity) {
    return ;
  }
  entity.IsStarred = true;
  entity.Stars = event.params.NewStarNo;
  let member = Memeber.load(event.params.CreatorId.toString());
  // You have to be a member to be able to staR a meme
  if(!member){
    return;
  }

  let Thisaddress = member.Adddress;
  let memes = entity.StarredAddresses
  if(!memes){
    return;
  }
  memes.push(Thisaddress);
  entity.StarredAddresses = memes;
  entity.save()
  member.StarredMemes = event.params.CreatorStarredMemes;
  member.save()


}

export function handleUnLikingMeme(event: UnLikingMeme): void {
  let entity = Meme.load(event.params.MemeId.toString())

  if(!entity) {
    return ;
  }

  entity.Likes = event.params.NewLikesNo;
  let member = Memeber.load(event.params.CreatorId.toString());
  // You have to be a member to be able to like a meme
  if(!member){
    return;
  }
  let Thisaddress = member.Adddress;
  let memes = entity.LikesAddresses
  if(!memes){
    return;
  }
  let index = memes.indexOf(Thisaddress)
  memes.splice(index,1);
  entity.LikesAddresses = memes;
  entity.save()
}

export function handleUnStarringMeme(event: UnStarringMeme): void {
  let entity = Meme.load(event.params.MemeId.toString())
  if(!entity) {
    return ;
  }
  entity.IsStarred = false;
  entity.Stars = event.params.NewStarNo;


  let member = Memeber.load(event.params.CreatorId.toString());
  // You have to be a member to be able to like a meme
  if(!member){
    return;
  }
  let Thisaddress = member.Adddress;
  let memes = entity.StarredAddresses
  if(!memes){
    return;
  }
  let index = memes.indexOf(Thisaddress)
  memes.splice(index,1);
  entity.StarredAddresses = memes;
  entity.save()

  member.StarredMemes = event.params.CreatorStarredMemes;
  member.save()
}
Enter fullscreen mode Exit fullscreen mode

Then save and deploy by running this in your terminal

yarn deploy
Enter fullscreen mode Exit fullscreen mode

When its done your subgraph should now be 100% synced

Synced

Then now you can open your subgraph playground and play around with it, but there wont be any data in there yet due to the fact that no data has been emitted out to the graph's node, which we'll do in the frontend part (PART TWO).

Well done! you've successfully done the following:

  • Created a nextJs project and Installed Hardhat
  • Wrote your smart contract
  • Tested it and Deployed it
  • and connected it to a graph's node by creating a subgraph.

Congratulations!!!!!!!!!!!!!!!!!!!!πŸŽ‰πŸŽ‰πŸš€πŸš€πŸ’–πŸŽŠ
You have built half of a Full stack dapp.

If you have any questions you can comment below.

Top comments (11)

Collapse
 
innbuld profile image
Olanrewaju Abdulmuizz

We’ll explanatory

Collapse
 
speedwelltaxis profile image
Speedwelltaxis

HI sir how can i make app for my business website could you help me.

Collapse
 
oleanji profile image
Adebayo Olamilekan

Hello, I will love to assist. What exactly do you want me to help you with? Can talk privately?

Collapse
 
thorstenhirsch profile image
Thorsten Hirsch

To paraphrase Darth Vader:

I find your lack of lower-case consistency in variables and function names disturbing.

πŸ€ͺ

But thank you for this tutorial - it really gives a good overview of the different parts one can use to build a dapp. I hadn't checked out thegraph, yet, but will do so thanks to your tutorial.

Collapse
 
oleanji profile image
Adebayo Olamilekan

hahaha...
You're welcome and the graph is really awesome

Collapse
 
vishal590 profile image
vishal


function GetmemberByAddr(address _member) external view returns(MemeMembers[] memory){
uint currentMemberNum = NumOfAllMembers.current();
uint currentIndex = 0;
MemeMembers[] memory foundMember = new MemeMembers[](1);
for(uint i = 0; i < currentMemberNum; i++){
if(_member == IdMembers[i + 1].MemeberAddress)
}
}

from where this _member address gets ?

Collapse
 
jofawole profile image
johnfawole.sol

Wow, this is thorough and insightful!

Collapse
 
vishal590 profile image
vishal

after pasting this command getting an error

$ graph init --product hosted-service vishal590/nft-air-by-curious

error

bash: graph: command not found

Collapse
 
abdirahman5863 profile image
Abdirahman Abdi

npm graph graph init --product hosted-service vishal590/nft-air-by-curious

Collapse
 
vishal590 profile image
vishal

what is this in fetchAllMemes() function ?

MemeFiles storage memeFiles = IdMemeFiles[currenNum];

Collapse
 
agastya27 profile image
Agastya sharma

Typescript.ts is auto generated or we have to modify and write it?