React+Hardhat+Ts快速构建本地Dapp-NFT发行系统
首先,用npx命令创建一个typescript React应用程序。
npx create-react-app geno-nft-dapp --template typescript

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

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

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


此命令是初始化hardhat,将其注入集成至项目结构中
安装hardhat-toolbox
npm install --save-dev @nomicfoundation/hardhat-toolbox

此时项目结构如下:

注意,"@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就可以启动项目
首先安装OpenZeppelin。 OpenZeppelin Contracts 是一个用于安全智能合约开发的库。,它提供了 ERC20 和 ERC721 等标准的实现 npm install @openzeppelin/contracts

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

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);
    }
}在编译之前,我们先对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

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

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

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

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

执行部署命令
npx hardhat run .\scripts\deploy.ts --network localhost
 此时,我们已经成功将合约部署到本地链上,并打印出了合约地址。
此时,我们已经成功将合约部署到本地链上,并打印出了合约地址。

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 };
} 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!