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