Как создать FullStack Dapp с помощью Next JS, Tailwind CSS и The graph. (Часть 1)

Здравствуйте 🖐🏽, Это письменный учебник по децентрализованному приложению Fullstack с использованием некоторых технологий web3, я называю это приложение NFT AIR.

Идея этого приложения заключается в том, чтобы позволить людям демонстрировать свои работы другим, но не для продажи. Это могут быть мемы, фотографии, рисунки, эскизы и т.д.

И я знаю, что вы думаете и НЕТ это не как Opensea типа Dapp или веб-3 версии insta, и да, это немного похоже на эти приложения, как люди могут загружать свои работы для просмотра, которые могут быть либо скачать или нет также видеть, а также может реагировать на сообщения других.

Полный код, который мы рассмотрим в этом учебнике, доступен на моем GitHub здесь, вы можете проверить его по мере продвижения по учебнику, полностью развернутый веб-сайт также находится здесь.

[ПРИМЕЧАНИЕ: Если вы хотите улучшить какую-то функцию, вы можете сделать Pull-запрос или поднять проблему, я обязательно рассмотрю их и будем работать над ними вместе].

Предварительный просмотр сайта NFT AIR

Предварительный просмотр Dapp (выше) — это то, что будет достигнуто в этом руководстве, и как я уже говорил, я дал ему название NFT AIR (не спрашивайте об этом, я знаю, что это отстой 🙈).
К сожалению, это только первая часть полного Dapp, так что эта часть только объясняет и показывает, как работает бэкенд, используя эти технологии:

  • Solidity: Язык, используемый для написания бэкенда Dapp.
  • Hardhat: Это среда разработки Ethereum, которую мы будем использовать в этом учебнике.
  • Графовый протокол: Используется для индексации наших данных, которые будут загружены в блокчейн.
  • Ankr: это наш провайдер rpc-узлов.
  • Polygon Mumbai Testnet: Блокчейн, который мы будем использовать в этом учебнике.
  • Ethers.js: библиотека веб-клиента Ethereum, используемая для взаимодействия с блокчейном.

Трудности и размышления

Я не столкнулся с большими трудностями при создании бэкенда, так как я уже делал нечто подобное в предыдущем проекте, но это мой первый раз, когда я использую протокол The graph, читал о провайдере Ankr node и использовал его для Dapp-проекта, в основном я использую Alchemy или Infura.

Обзор проекта

Давайте посмотрим на бэкенд этого проекта NFT AIR в более простой манере, если да, то вот что мы будем делать в этой части

  1. Установка зависимостей
  2. Написание смарт-контракта на языке Solidity
  3. Тестирование смарт-контракта и Hardhat Config.
  4. Развертывание смарт-контракта
  5. Настройка подграфа.
  6. Отслеживание и хранение данных, полученных с помощью индексаторов графа.

Необходимые условия

  1. Текстовый редактор по вашему выбору, установленный на вашей машине (желательно Vs code).
  2. Node.js также должен быть установлен.
  3. И самое главное, чтобы рядом с вами была бутылка воды/сока 😉.

Создание и настройка проекта

Начнем! (Сделайте глоток того напитка, который у вас есть, и начинайте)

  • Next app В командной строке/терминале укажите место, где вы хотите установить dapp, а затем запустите приведенный ниже код с помощью npm или yarn или pnpm, любой на ваш выбор, чтобы создать next app
yarn create next-app nft-air-dapp-tutorial
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Установка зависимостей Нам нужно установить несколько зависимостей перед написанием любой строки кода, мы установим: hardhat, ether.js. (Примечание: в пути «nft-air-dapp-tutorial»).
  yarn add ethers hardhat @nomiclabs/hardhat-waffle ethereum- 
  waffle chai @nomiclabs/hardhat-ethers @openzeppelin/contracts 
  dotenv
Вход в полноэкранный режим Выйти из полноэкранного режима

Вы также можете использовать «npm install» вместо yarn add

  • Среда разработки Hardhat

В этом руководстве мы будем использовать hardhat в качестве среды разработки Ethereum, существуют и другие, такие как foundry, Truffle.

yarn hardhat
Вход в полноэкранный режим Выйти из полноэкранного режима

После выполнения приведенной выше строки вам должны быть заданы некоторые вопросы о пути и типе проекта и т.д. . Выполните их правильно, а затем откройте ваше приложение в vs code, используя приведенный ниже код в терминале.

code .
Вход в полноэкранный режим Выйти из полноэкранного режима

После этого должно открыться окно Visual Studio с некоторыми файлами и папками во вкладке «Проводник», которое должно выглядеть следующим образом

Приведенное выше изображение должно быть похоже на структуру файлов в вашем текстовом редакторе.

  • contracts: Эта папка будет содержать файлы наших смарт-контрактов, написанных на solidity.

  • node_modules : Эта папка содержит установленные зависимости, которые будут использоваться в проекте.

  • pages: Эта папка предназначена для фронтенда, в которой будут храниться различные страницы нашего dapp.

  • public: Это также папка frontend, в которой будут храниться наши медиафайлы.

  • scripts: В этой папке хранится сценарий развертывания нашего контракта.

  • styles: Как и название, содержит файлы, связанные со стилизацией для фронтенда этого dapp.

  • test: Эта папка, как следует из названия, содержит тестовые скрипты, написанные на JavaScript, для тестирования нашего смарт-контракта перед развертыванием.

Конфигурация

В файл hardhat.config.js, расположенный в корневом каталоге вашего проекта, вы можете вставить приведенный ниже код (я использовал solidity версии 0.8.7, ваша может быть выше).

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

module.exports = {
  solidity: "0.8.7",
  networks: {
    hardhat:{
      chainId:1337
    },
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь устанавливается используемая версия solidity и локальная сеть hardhat.

Умный контракт

Код смарт-контракта для этого проекта немного многоват и может показаться непосильным на первый взгляд, но я объясню все функции.
Полный код контрактов на Solidity находится здесь.
Для начала создайте новый файл в папке с контрактами, назовите его Nft-Air.sol и вставьте в него этот код.

(Если у вас появляются ошибки в строках импорта, установите Solidity и расширение hardhat в коде vs )

//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//////
/////////////////////////////

}
Войдите в полноэкранный режим Выйти из полноэкранного режима

Объяснение полного контракта по частям, приведенное выше, содержит:

  • Импортированные библиотеки,
  • Глобальные переменные, используемые в контракте,
  • сопоставления, структуры и события.

В этой части смарт-контракта нет никаких функций, только переменные.

В приведенном выше контракте после импорта пары библиотек из openzeppelin, а также для записи в консоль используется console.sol из hardhat.

Определены две структуры, которые представляют детали для обоих членов dapp и их артов (nft created).
Затем следует ряд связок, которые используются для отслеживания некоторых переменных.

Наконец, создаются 6 различных событий, которые представляют собой действия, влияющие на/изменяющие данные, хранящиеся на блокчейне, они будут испускаться позже, что послужит сигналом для подграфа.

Остальной код смарт-контракта (ниже)

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;
    }

Вход в полноэкранный режим Выход из полноэкранного режима

Может возникнуть путаница из-за часто используемого слова «мемы», это потому, что этот dapp является обновлением этого под названием Memeforest.

Приведенные выше функции должны быть размещены в указанной области в первой половине смарт-контракта.

Приведенные выше функции являются самообъясняющимися, так как их названия отражают то, что они делают.

Тестирование смарт-контракта

Мы должны протестировать наш смарт-контракт перед развертыванием, чтобы найти ошибки, которые нужно исправить (все ошибки нужно исправлять, хотя lol xD ).

Для этого откройте папку test в корневом каталоге проекта, откройте файл с именем smaple-text.js и вставьте в него приведенный ниже код (здесь мы пишем наши тесты на 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)

  });

});

Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы запустить приведенный выше код, выполните следующий код

yarn hardhat test

or

npx hardhat test
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы должны получить следующее сообщение после серии логов, которые появятся на терминале => ✔ Should create two members, ceate two nft, like one, star one, fetch all, fetch starred и fetch mine

После получения успешного сообщения мы можем развернуть наш контракт, а затем настроить наш подграф.

Развертывание

Код ниже развертывает наш смарт-контракт, а также записывает контактный адрес нашего контракта в файл, который мы сейчас создадим под названием constant.js в корневом каталоге нашего проекта.

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);
  });

Вход в полноэкранный режим Выйти из полноэкранного режима

Новый файл под названием constant.js будет создан в корне проекта, но перед этим нам нужно развернуть наш контракт, а для этого нам нужно сначала обновить наш
hardhat.config.js, так как мы будем использовать конечную точку rpc от ankr, а также использовать наш приватный ключ.

*Файл *.env

  • Создайте файл с именем ‘.env’ в основании проекта (т.е. не внутри какой-либо папки), затем вставьте в него этот код
ANKR_ID = ""
PRIVATE_KEY=" "
Войти в полноэкранный режим Выйти из полноэкранного режима

Нам нужно заполнить пустые кавычки, для ANKR_ID посетите ankr и получите конечную точку mumbai testnet rpc здесь.

Скопируйте указанную конечную точку и вставьте в файл .env.

Для получения закрытого ключа вам необходимо установить Metamask в вашем браузере.

  • Сначала откройте данные своей учетной записи
  • Нажмите на кнопку Экспорт закрытого ключа
  • Введите пароль Metamask, а затем скопируйте ваш закрытый ключ.

*ПРИМЕЧАНИЕ: НЕ СООБЩАЙТЕ НИКОМУ СВОЙ ЗАКРЫТЫЙ КЛЮЧ*.

Вставьте ключ в переменную PRIVATE_KEY в вашем файле .env.

Файл hardhat.config.js должен быть обновлен до кода, приведенного ниже

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],
    },
  },


};
Вход в полноэкранный режим Выйти из полноэкранного режима

Вот и все, что нам нужно для развертывания нашего смарт-контракта. После этого мы можем запустить следующее

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

or 

yarn hardhat compile
yarn hardhat run scripts/deploy.js --network mumbai
Войти в полноэкранный режим Выйти из полноэкранного режима

Это развернет ваш смарт-контракт и заполнит файл constant.js адресом вашего контракта.

Настройка подграфа

Протокол graph — это децентрализованный протокол запросов для индексации блокчейна. Чтобы понять больше о графе, я предлагаю вам прочитать документацию.

Прежде чем мы продолжим, я надеюсь, что у вас на машине установлен yarn, если нет, получите yarn здесь.

Краткое объяснение того, как работает граф-протокол: в нашем смарт-контракте, когда мы добавляем/изменяем данные в блокчейне, мы испускаем события, содержащие эту информацию, и она сохраняется в узле grph, а когда она нам понадобится, мы можем запросить данные, как показано на рисунке ниже (с их сайта).

Чтобы начать работу, вам нужно создать свой собственный подграф, зайдя на сайт The graph hosted service, где вы создадите учетную запись, если у вас ее нет, и перейдете на панель управления, затем нажмите на кнопку Add Subgraph.


При создании подграфа вы заполните некоторые детали о подграфе для создаваемого вами dapp.

После заполнения подробностей о подграфе вы попадаете на страницу со следующими шагами и командами, которые необходимо выполнить, как показано на рисунке ниже.

  • Сначала нам нужно запустить программу (либо yarn, либо npm) для установки некоторых необходимых пакетов.

  • После установки пакетов вам нужно создать файл с именем abi.json в корне вашего проекта, затем перейти в подпапку contracts в папке artifacts, где вы увидите еще одну папку с именем вашего контракта, откройте ее и откройте файл с таким форматом YOUR-CONTRACT-NAME.json.

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

Затем скопируйте abi (начиная с квадратных скобок и заканчивая единицей, затем вставьте скопированный код в созданный вами файл abi.json).

Затем продолжайте выполнять команды в шаге Init .

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

Вход в полноэкранный режим Выйти из полноэкранного режима

Запустите вышеуказанную команду, изменив «GITHUB_USER» на ваше имя пользователя GitHub, а «SUBGRAPH NAME» на имя, которое вы дали этому подграфу.

Продолжайте выполнять команду (помните, что сеть — это Мумбаи).

Напишите в abi.json, где он просит указать путь к файлу, выполните остальные действия, и он создаст подграф.

Затем выполните следующие шаги, как описано выше.

Ключ развертывания — это «Ключ доступа» с комбинацией цифр и букв на странице вашего подграфа.

Затем запустите

yarn deploy
Войдите в полноэкранный режим Выйти из полноэкранного режима

указывая на вашу новую папку с субграфом.

Теперь там должна появиться ссылка, ведущая на ваш новый развернутый подграф

Alas!!!!!!! Мы развернули наш субграф.

Прошло некоторое время, поэтому я предлагаю вам сделать глоток из вашего напитка.

А теперь,

Как должен выглядеть ваш подграф в развернутом виде

Откройте папку сборки и перейдите к файлу «subgraph.yaml» и измените следующее

добавив startBlock: BLOCK_NUMBER после строки abi.

Номер вашего блока можно получить, вставив ваш адрес в MumbaiPolygonScan, затем прокрутите вниз до момента создания контракта и скопируйте номер вашего блока, как показано ниже в кружочке.

Готово? Хорошая работа.

Откройте файл schema.graphl над файлом sugbraph.yaml и вставьте туда код.

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!
}

Войти в полноэкранный режим Выйдите из полноэкранного режима

[Как я уже говорил, вас может смутить слово memes, которое я чаще всего использую, это потому, что nft air является расширением Memeforest, и вы можете изменить слова meme на nfts или на что угодно].

Приведенная выше схема содержит сущности, которые представляют структуры данных, которые будут храниться в узле графа.

У нас две сущности, потому что мы храним только

  • данные о членах и
  • их nft/мемы/арты, которые они загружают.

Запустите

yarn codegen
Войти в полноэкранный режим Выйти из полноэкранного режима

Сгенерировать файл кода, после чего вставить приведенный ниже код в файл typescript (.ts) внутри папки src в вашем сабграунде

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()
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем сохраните и разверните, выполнив следующее в терминале

yarn deploy
Войти в полноэкранный режим Выйдите из полноэкранного режима

После этого ваш подграф должен быть синхронизирован на 100%.

Теперь вы можете открыть игровую площадку вашего подграфа и поиграть с ним, но там еще не будет никаких данных из-за того, что данные не были выданы на узел графа, что мы сделаем во фронтенде (ЧАСТЬ ДВА).

Отлично! Вы успешно выполнили следующие действия:

  • Создали проект nextJs и установили Hardhat
  • Написали свой смарт-контракт
  • протестировали его и развернули
  • и подключили его к узлу графа, создав подграф.

Поздравляем!!!!!!!!!!!!!!!!!!!!🎉🎉🚀🚀💖🎊
Вы создали половину полностекового dapp.

Если у вас есть вопросы, вы можете прокомментировать их ниже.

Оцените статью
devanswers.ru
Добавить комментарий