如何使用 Next.js、Tailwind CSS 和 Graph 创建全栈 DApp。(第一部分)
大家好🖐🏽,这是一篇关于使用一些 Web3 技术构建全栈去中心化应用程序的教程,我称这个 dapp 为NFT AIR。
这款去中心化应用(DApp)的理念是让人们可以向他人展示自己的艺术作品,但并非用于出售。作品形式可以是表情包、图片、绘画、素描等等。
我知道你在想什么,不,它不像 Opensea 那样的 Dapp,也不像 Instagram 的 web3 版本。是的,它和那些应用有点类似,因为人们可以上传自己的作品供人欣赏,这些作品可以下载,也可以不下载,还可以查看和回应其他人的帖子。
本教程中我们将要讲解的完整代码可以在我的 GitHub 上找到,您可以随着教程的进行查看它,完全部署的网站也在这里。
【注:如果您想改进某些功能,欢迎提交 Pull Request 或提出问题,我一定会查看并与您一起解决。】
NFT AIR 网站预览
上面的 DApp 预览就是本教程要实现的效果,就像我之前说的,我给它取的名字是NFT AIR(别问我为什么,我知道这名字很烂🙈)。
很遗憾,这只是完整 DApp 的第一部分,所以这部分只解释和展示了如何使用这些技术实现后端功能:
- Solidity:用于编写 Dapp 后端的语言。
- Hardhat:这是我们将在本教程中使用的以太坊开发环境。
- 图协议:用于索引我们将要上传到区块链的数据。
- Ankr:这是我们的 RPC 节点提供程序
- Polygon Mumbai 测试网:本教程中我们将使用的区块链。
- Ethers.js:以太坊 Web 客户端库,用于与区块链进行交互。
挑战与思考
由于我在之前的项目中做过类似的事情,所以在构建后端时并没有遇到太多挑战。但是,这是我第一次使用图协议,也是第一次阅读和使用Ankr节点提供程序来开发 Dapp 项目。我通常使用Alchemy或Infura。
项目概述
让我们以更简单的方式来看一下这个 NFT AIR 项目的后端。如果是这样,那么我们将在本部分完成以下工作。
- 安装依赖项
- 使用 Solidity 语言编写智能合约
- 测试智能合约和 Hardhat 配置。
- 智能合约的部署
- 建立子图。
- 跟踪和存储我们使用图索引器发出的数据
先决条件
项目建设与搭建
开始吧!(喝一口你手里的饮料,然后开始!)
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 作为以太坊开发环境,还有其他一些环境,例如foundry、Truffle。
yarn hardhat
运行上述命令后,系统会询问一些关于项目路径和类型等问题。请正确填写,然后在终端中使用以下代码在 VS Code 中打开您的应用程序。
code .
然后,您的 Visual Studio 窗口应该会打开,在“资源管理器”选项卡中应该会显示一些文件和文件夹,如下所示。
上图应该与您文本编辑器中的文件结构类似。
-
合约:此文件夹将包含我们用 Solidity 编写的智能合约文件。
-
node_modules:此文件夹包含项目中将要使用的已安装依赖项。
-
pages:此文件夹用于存放前端部分,我们的 dapp 的不同页面将存放在这里。
-
public:这也是一个前端文件夹,用于存放我们的媒体文件。
-
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 版本和本地安全帽网络。
智能合约
这个项目的智能合约代码有点多,乍一看可能有点复杂,但我会逐一解释每个函数。
完整的合约 Solidity 代码在这里。
首先,请在合约文件夹中新建一个名为Nft-Air.sol 的文件,并将这段代码粘贴到其中。
(如果导入语句出现错误,请在 VS Code 中安装 Solidity 和 Hardhat 扩展。)
//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导入了几个库,并且使用 hardhat 中的 console.sol 向控制台写入数据。
这里定义了两个结构体,分别表示去中心化应用(dapp)成员及其作品(NFT 作品)的详细信息。
之后是一系列映射,用于跟踪一些变量。
最后创建了 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;
}
由于“memes”一词被频繁使用,可能会引起一些困惑,这是因为这个 dapp 是名为Memeforest的dapp的升级版。
上述功能应放置在智能合约前半部分的指定区域内。
以上函数的名称已经很清楚地说明了它们的功能。
测试智能合约
我们必须先测试智能合约,然后再部署,以找出需要修复的错误(虽然所有错误都需要修复,哈哈 xD)。
为此,请打开项目根目录下的测试文件夹,打开名为 sample-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
在终端显示一系列日志后,您应该会看到这条消息 => ✔ 应该创建两个成员,创建两个 NFT,点赞一个,给其中一个加星标,获取所有 NFT,获取已加星标的 NFT,以及获取我创建的 NFT。
收到成功消息后,我们现在可以部署合约,然后设置子图。
部署
以下代码会部署我们的智能合约,并将合约的联系人地址写入一个名为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文件,因为我们将使用来自ankr的 rpc 端点,并且还要使用我们的私钥。
* .env 文件 *
- 在项目根目录(即不在任何文件夹内)创建一个名为“.env”的文件,然后将以下代码粘贴到该文件中。
ANKR_ID = ""
PRIVATE_KEY=" "
我们需要填充空的引号,对于 ANKR_ID,请访问ankr并在此处获取孟买测试网 rpc 端点。
复制上述端点并粘贴到 .env 文件中。
要获取私钥,您需要在浏览器中安装Metamask 。
- 首先打开您的账户详情
- 点击“导出私钥”按钮
- 输入您的 Metamask 密码,然后复制您的私钥。
*注意:切勿与任何人分享您的私钥 *
将密钥粘贴到 .env 文件中的 PRIVATE_KEY 变量中。
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 文件中。
子图设置
图协议是一种用于索引区块链的去中心化查询协议。要了解更多关于图的信息,我建议您阅读相关文档。
在我们继续之前,我希望你的机器上已经安装了毛线,如果没有,请在这里购买毛线。
简要解释一下图协议的工作原理;在我们的智能合约中,当我们在区块链上添加/更改数据时,我们会发出包含该信息的事件,该信息会存储在图的节点中,当我们需要取回该信息时,我们可以像下图所示(来自他们的网站)那样查询数据。
首先,您需要创建自己的子图。前往图托管服务,如果您还没有帐户,请创建一个帐户,然后转到您的仪表板,然后单击“添加子图”按钮。

创建子图时,您需要填写一些关于您正在构建的 dapp 的子图的详细信息。
填写完子图的详细信息后,您将被带到一个页面,其中显示了接下来的步骤和要遵循的命令,如下图所示。
- 首先我们需要运行这个命令(yarn 或 npm)来安装一些所需的软件包。
- 安装软件包后,您需要在项目根目录下创建一个名为abi.json的文件,然后进入artifacts文件夹中的 contracts 子文件夹,您会看到另一个以您的合约名称命名的文件夹,打开它并打开格式为YOUR-CONTRACT-NAME.json 的文件。
[artifacts/contracts/YOUR-CONTRACT-NAME.json]
然后复制 abi(以方括号开头,以 1 结尾),然后将复制的代码粘贴到您创建的 abi.json 文件中。
然后继续按照初始化步骤中的命令操作。
graph init --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME>
运行上述命令,将“GITHUB_USER”更改为您的GitHub用户名,将“SUBGRAPH NAME”更改为您为此子图指定的名称。
继续执行命令(记住网络是孟买的)。
在 abi.json 文件中,当它要求输入文件路径时,按照其余步骤操作,即可生成子图。
然后按照上述步骤进行操作。
部署密钥是子图页面中的“访问密钥”,由数字和字母组合而成。
然后运行
yarn deploy
指向您的新子图文件夹。
现在应该有一个链接指向您新部署的子图。
啊!!!我们已经部署了子图。
好久不见了,我建议你喝一口你手里的饮料。
那么,
展开后的子图应该是什么样子
打开构建文件夹,找到“subgraph.yaml”文件,在 abi 行之后 添加startBlock: BLOCK_NUMBER进行更改。
您可以通过将您的地址粘贴到MumbaiPolygonScan中,然后向下滚动到合同创建时间并复制您的区块编号来获取您的区块编号,例如下图圆圈中的数字。
完成了吗?干得好。
打开 schema.graphl 文件(位于 schema.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!
}
[就像我之前说的,你可能会对我主要使用的“meme”这个词感到困惑,这是因为nft air是Memeforest的扩展,你可以把meme这个词改成nfts或者任何你想要的词。]
上述模式包含实体,这些实体表示将存储在图的节点中的数据结构。
由于我们只存储数据,所以我们有两个实体。
- 会员详情和
- 他们上传的nft/表情包/艺术作品。
跑步
yarn codegen
要生成一些代码文件,请将以下代码粘贴到子图的 src 文件夹中的 TypeScript (.ts) 文件中。
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%同步。
现在你可以打开你的子图游乐场并进行尝试,但由于还没有数据发送到图的节点,所以其中还没有任何数据,我们将在前端部分(第二部分)中完成这项工作。
做得好!你已成功完成以下操作:
- 创建了一个 Next.js 项目并安装了 Hardhat。
- 编写你的智能合约
- 测试并部署了它
- 并通过创建子图将其连接到图的节点。
恭喜!!!!!!!!!!🎉🎉🚀🚀💖🎊
你已经完成了一个全栈 dapp 的一半。
如有任何问题,请在下方留言。
文章来源:https://dev.to/oleanji/how-to-create-a-fullstack-dapp-using-next-js-tailwind-css-and-the-graph-part-1-2e77


















