用React+Hardhat快速构建本地Dapp-NFT发行系统

  • 胡花花
  • 更新于 2024-05-13 15:06
  • 阅读 2163

React+Hardhat+Ts快速构建本地Dapp-NFT发行系统

一、环境准备

1. 技术栈
  • node.js 18.18.0
  • react 18.2.0
  • hardhat 2.13.0
  • openzeppelin 4.9.6
  • IPFS Desktop
  • solidity ^0.8.20
  • vscode 1.87.2
  • ethers.js 6.11.1
    2. 主要技术简介
  • hardhat Hardhat是一个编译、部署、测试和调试以太坊应用的开发环境。而且Hardhat内置了Hardhat网络,这是一个专为开发设计的本地以太坊网络。主要功能有Solidity调试,跟踪调用堆栈、交易失败时的明确错误信息提示等。
  • ethers.js ethers.js库旨在为以太坊区块链及其生态系统提供一个小而完整的 JavaScript API 库。 相较于web3.js,ethers.js更轻量更灵活,能提升dapp的性能。 ethers.js有四大核心模块:Provider、Signer、Contract、utils。其中Provider用来读取区块链网络和获取链上状态,Signer主要是进行签名进行以太币交易,Contract是合约的抽象,可以用来操作合约,utils主要是进行格式化钱包余额等数据处理。

二、构建步骤

1.用react快速创建web3应用程序

首先,用npx命令创建一个typescript React应用程序。 npx create-react-app geno-nft-dapp --template typescript

image.png

接下来,进入到新目录,此时创建的项目是一个普通的前端应用程序。

image.png

接下来安装hardhat并集成到项目中 npm install --save-dev hardhat

image.png

删除 目录下README.md 和 tsconfig.json文件,防止与下述步骤命令执行产生冲突 npx hardhat init

image.png

image.png

此命令是初始化hardhat,将其注入集成至项目结构中

安装hardhat-toolbox npm install --save-dev @nomicfoundation/hardhat-toolbox

image.png

此时项目结构如下:

image.png

注意,"@types/node"版本需在18.0.0以上,不然会与hardhat-toolbox产生依赖冲突。

最后,修改tsconfig.json文件如下

{
  "compilerOptions": {
    "jsx": "preserve",
    "target": "es2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": [
    "src//*"                             // 包含src目录下的所有文件
  ],
  "exclude": [
    "node_modules",                        // 排除node_modules目录
    "/*.spec.ts"                         // 排除所有的测试文件
  ]
}

此时运行npm start就可以启动项目

2.安装OpenZeppelin

首先安装OpenZeppelin。 OpenZeppelin Contracts 是一个用于安全智能合约开发的库。,它提供了 ERC20 和 ERC721 等标准的实现 npm install @openzeppelin/contracts

image.png

安装完成后,可以在项目node_modules目录下看到Openzeppeline结构如下:

image.png

access: 此目录提供了限制谁可以访问合同功能或何时可以访问的方法 crosschain:此目录提供了提高智能合约跨链意识的构建块 finance:此目录包括金融系统的基元: PaymentSplitter允许在一组账户中分割以太币和ERC20付款。发送方不需要知道资产将以这种方式分割,因为它是由合同透明地处理的。分割可以是相等的部分,也可以是任何其他任意的比例。 VestingWallet为特定受益人处理以太币和ERC20代币的归属。可以将多个代币的托管权交给本合同,该合同将按照给定的、可定制的行权时间表将代币发放给受益人。 goverance:This directory includes primitives for on-chain governance 此目录包括链上治理的基元 interfaces:这些接口可用作.sol文件,也可用作编译器.json ABI文件(通过npm包)。这些对于与实施它们的第三方合同进行交互非常有用 metax: 这是一组实现不同代理模式的低级别合约,可升级也可不升级。有关此模式的深入概述,请查看代理升级模式页面。 proxy: security:这些合同旨在涵盖常见的安全做法。 PullPayment:一种可以用来避免重入攻击的模式。 重入保护:一个可以在某些函数中防止重入的修饰符。 可暂停:一种常见的应急响应机制,可以在补救挂起时暂停功能 token: utils:包含实用程序函数的杂项约定和库,可用于提高安全性、使用新的数据类型或安全地使用低级基元。 Address、Arrays、Base64和Strings库提供了更多与这些本机数据类型相关的操作,而SafeCast添加了在不同的有符号和无符号数字类型之间安全转换的方法。Multicall提供了一个函数,用于在单个外部调用中将多个调用批处理在一起。 对于新的数据类型: 计数器:获取只能递增、递减或重置的计数器的简单方法。对于ID生成、计算合同活动等非常有用。 EnumerableMap:类似于Solidity的映射类型,但具有键值枚举:这将让您知道映射有多少个条目,并对它们进行迭代(这在映射中是不可能的)。 EnumerableSet:类似于EnumerableMap,但适用于集合。可用于存储特权帐户、已颁发的ID等。 ERC721合约继承关系图结构 [图片] ERC721URIStorage:是 tokenId 到 tokenURI 映射关系的实际持有者 ERC721Enumrable:提供遍历代币的能力 ERC721Metadata: Metadata 层面的通用属性,包括token名称/存储地址(metadata URI)等 ERC165:检查一个智能合约是不是支持ERC721

合约示例

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract GenoNFT is ERC721URIStorage, ERC721Enumerable {

    uint256 private _nextTokenId;
    constructor() ERC721("GenoNFT", "GT") {

    }

    function mint(address player, string memory tokenUri)
        public
        returns (uint256)
    {

        uint256 tokenId = _nextTokenId++;
        _mint(player, tokenId);
        _setTokenURI(tokenId, tokenUri);

        return tokenId;
    }

    function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) {
       super._increaseBalance(account, value);
    }

    function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Enumerable) returns (address) {
        return super._update(to, tokenId, auth);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721URIStorage, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return ERC721URIStorage.tokenURI(tokenId);
    }
}
3.编译合约

在编译之前,我们先对hardhat.config.ts文件做一些修改:


import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  paths:{
    artifacts: "./src/artifacts"
    }
};

export default config;

此配置用来指定合约语言solidity的版本以及合约编译后的输出路径。 接下来在项目目录下执行如下命令对合约进行编译: npx hardhat compile

image.png

合约编译完成后会在上面配置的目录下生成一些文件, [图片] 这里需要重点关注的是GenoNFT.json文件,它包含了合约的ABI信息。ABI代表应用程序二进制接口,是客户端应用程序和以太坊区块链之间的接口。

4.部署合约

执行npx hardhat node命令来启动harhat自带的本地链

image.png

接下来在metamask中配置好本地链网络并随意选中上图中的账户导入到metamask中

53fc1477-60b0-4915-a164-b59413ab46f7.png

复制账户私钥将其导入值metamask中

11f505cd-9ebb-4c32-bb24-72aace858c9d.png

创建部署脚本文件deploy.ts来部署合约到本地链上,部署脚本如下

7137cb71-8a95-42fa-be5b-fb018cbc75c2.png

执行部署命令 npx hardhat run .\scripts\deploy.ts --network localhost

808827a8-a1f4-40d1-8c4a-715578abc77b.png 此时,我们已经成功将合约部署到本地链上,并打印出了合约地址。

三、实现铸币发布NFT功能

1.铸币功能流程

screenshot-20240513-150140.png

2.相关核心代码
const mint = async () => {
        try {
            const data: NftMeta = { ...meta, imageUri: uri,type:"image" }
            const json = JSON.stringify(data);
            //上传文件到Ipfs(json)
            const metauri = await storeMeta(json); 
            //铸造nft
            const { success, tokenId } = await mintNFT(metauri);
        } catch (error) {
            if (error instanceof Error)
                messageBox("danger", "", error.message)
        }
    }

    export const mintNFT = async (tokenUri: String): Promise<{ success: boolean, tokenId?: number }> => {

    //连接钱包
    const { success, address, provider, signer } = await trying();
    if (!success || !signer) {
        return { success: false };
    }
    //构造合约
    let nft = new Contract(configuration().nftAddress, NFT.abi, signer);
    //调用合约铸币方法
    let transaction = await nft.mint(address, tokenUri);
    let tx = await transaction.wait(1);

    let logs = tx.logs[1];
    let nftIndex = parseInt(logs.data, 16);
    let tokenId = Number(nftIndex);
    alert(nftIndex);
    return { success: true, tokenId };
}
点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
胡花花
胡花花
0x9322...cf41
江湖只有他的大名,没有他的介绍。