一、环境准备
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
接下来,进入到新目录,此时创建的项目是一个普通的前端应用程序。
接下来安装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就可以启动项目
2.安装OpenZeppelin
首先安装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); } }
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
合约编译完成后会在上面配置的目录下生成一些文件,
[图片]
这里需要重点关注的是GenoNFT.json文件,它包含了合约的ABI信息。ABI代表应用程序二进制接口,是客户端应用程序和以太坊区块链之间的接口。
4.部署合约
执行npx hardhat node命令来启动harhat自带的本地链
接下来在metamask中配置好本地链网络并随意选中上图中的账户导入到metamask中
复制账户私钥将其导入值metamask中
创建部署脚本文件deploy.ts来部署合约到本地链上,部署脚本如下
执行部署命令
npx hardhat run .\scripts\deploy.ts --network localhost
此时,我们已经成功将合约部署到本地链上,并打印出了合约地址。
三、实现铸币发布NFT功能
1.铸币功能流程
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 }; }
评论前必须登录!
注册