赞
踩
目录
在本文中,我将向您展示如何创建NFT合约,以及如何构建React Web3应用程序来加载NFT集合、生成NFT和提取功能。
以太坊是区块链世界中的大牌。它是第二大区块链平台,是开发基于区块链的去中心化应用程序的首选。以太坊重新定义了区块链技术的吸引力,并向世界展示了它不仅仅是一个点对点的现金系统。虽然新来者大多将比特币与区块链技术联系起来,但加密货币只是该技术的一个方面。但以太坊是可编程的区块链平台和开源。因此,用户可以开发他们选择的不同应用程序。以太坊创新了大量先进的概念,例如去中心化应用程序、智能合约、虚拟机、ERC代币等。
ERC基本上意味着以太坊征求意见,其基本作用是为以太坊提供功能。它具有一套用于在以太坊上创建代币的标准规则。ERC代币中的说明概述了代币的销售、购买、单位限制和存在。
ERC-20和ERC721代币是ERC代币标准的初始类型,在定义以太坊生态系统的功能方面发挥着重要作用。您可以将它们视为在以太坊区块链上创建和发布智能合约的标准。同样重要的是要注意,人们可以投资在智能合约的帮助下创建的代币化资产或智能属性。ERC更像是所有开发人员在开发智能合约时都应遵循的模板或格式。
ERC20与ERC721的区别是可替代性和不可替代性之间的差异。可替代资产是您可以与另一个类似实体交换的资产。另一方面,不可替代的资产正好相反,不能相互交换。例如,房屋很容易被视为不可替代的资产,因为它具有一些独特的属性。当涉及到加密世界时,以数字形式表示资产肯定必须考虑可替代性和不可替代性方面。
NFT智能合约是ERC721代币。它是一种不可替代的代币,称为NFT,是一种数字资产,代表现实世界的对象,如区块链上的艺术、音乐和视频。NFT使用在加密货币区块链上记录和认证的识别码和元数据,这使得区块链上代表的每个NFT都是唯一的资产。与同样记录在区块链上的加密货币不同,NFT不能等价交易或交换,因此它们是不可替代的。智能合约是存在于区块链中的编程。这使网络能够存储NFT交易中指示的信息。智能合约是自动执行的,可以检查合同条款是否得到满足,以及执行条款,而无需中介或中央机构。
Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态内账户行为的程序。
Solidity是静态类型的,支持继承、库和复杂的用户定义类型以及其他功能。使用Solidity,您可以创建用于投票、众筹、盲目拍卖和多重签名钱包等用途的合约。
Hardhat是以太坊开发环境。轻松部署合约、运行测试和调试Solidity代码,而无需处理实时环境。Hardhat网络是一个专为开发而设计的本地以太坊网络。
NFT代币具有元数据(图像和属性)。它可以存储在IPFS(星际文件系统)或链上。我们将我们的NFT元数据以SVG格式存储在IPFS上。Opensea的店面支持SVG图片格式,合约部署到ethereum区块链mainnet、testnet后,可以在其中查NFT。
在我们创建NFT合约之前,我们需要将SVG图像上传到IPFS。多亏了PinataCID网站,它使这项工作变得非常容易。转到Pinata网站并创建一个帐户,如果您上传最多1GB的数据,则免费。注册后,您将被带到Pin图管理器窗口。使用界面上传您的文件夹。上传文件夹后,您将获得与之关联的文件夹。它应该看起来像这样:
对于我的文件夹,是CIDQmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg。因此,此文件夹的IPFS URL是ipfs://QmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg。
此URL不会在浏览器中打开。为此,您可以使用IPFS网关的HTTP URL。尝试访问此链接:https://ipfs.io/ipfs/QmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg/0001.svg。它将显示我命名为 00001.png 并上传到我的文件夹的图像。
构建智能合约时,您需要一个开发环境来部署合约、运行测试和调试Solidity代码,而无需处理实时环境。
React App是将代码编译为可在客户端应用程序中运行的Solidity代码的完美应用程序。
Hardhat是一个专为全栈开发而设计的Ethereum开发环境和框架。
ethers.js是一个完整而紧凑的库,用于从客户端应用程序(如React、Vue、Angular)与Ethereum Blockchain及其生态系统进行交互。
MetaMask帮助处理帐户管理并将当前用户连接到区块链。连接他们的MetaMask钱包后,您可以与全球可用的Ethereum API(window.ethereum)进行交互,该API用于识别web3兼容浏览器的用户(如MetaMask用户)。
首先,我们创建一个typescript React应用程序。
npx create-react-app react-web3-ts --template typescript
接下来,切换到新目录并安装ethers.js和hardhat。
npm install ethers hardhat chai @nomiclabs/hardhat-ethers
首先从React应用程序文件夹中删除 README.md 和 tsconfig.json,否则会出现冲突。运行以下命令:
npx hardhat
然后选择“创建TypeScript项目”。
它会提示您安装一些依赖项。
Hardhat已安装。我们只需要安装hardhat-toolbox。
npm install --save-dev @nomicfoundation/hardhat-toolbox@^1.0.1
现在在VS Code中打开“react-web3-ts”文件夹,它应该是这样的:
有一个示例合约 Lock.sol。
从我们的React应用程序中,我们与智能合约交互的方式是使用ethers.js库、合约地址以及由hardhat根据合约创建的ABI。
什么是ABI?ABI代表应用程序二进制接口。您可以将其视为客户端应用程序和以太坊区块链之间的接口,您将在其中部署要与之交互的智能合约。
ABIs通常由开发框架(HardHat)从Solidity智能合约编译而成。您还经常可以在Etherscan上找到智能合约的ABIs。
Hardhat-toolbox包括type-chain。默认情况下,typechain在根文件夹下的typechain-types中生成键入内容。这将导致我们的React客户端编码出现问题。React会抱怨我们应该只从 src 文件夹导入类型。所以我们在hardhat.config.ts中进行了更改。
- import { HardhatUserConfig } from "hardhat/config";
- import "@nomicfoundation/hardhat-toolbox";
-
- const config: HardhatUserConfig = {
- solidity: "0.8.9",
- networks: {
- hardhat: {
- chainId: 1337
- }
- },
- typechain: {
- outDir: 'src/typechain-types',
- target: 'ethers-v5',
- alwaysGenerateOverloads: false, // should overloads with full signatures
- // like deposit(uint256) be generated always,
- // even if there are no overloads?
- externalArtifacts: ['externalArtifacts/*.json'], // optional array of glob
- // patterns with external artifacts to process (for example external libs
- // from node_modules)
- dontOverrideCompile: false // defaults to false
- }
- };
-
- export default config;
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
在hardhat.config.ts中,我添加了一个本地网络,请注意,如果要连接MetaMask,则必须将链ID设置为1337。此外,我们添加了一个自定义的类型链配置,其中我们的文件夹是 src/typechain-types。
在VS Code终端中运行以下命令。
npx hardhat compile
现在,您应该在 src 目录中看到一个名为类型链类型的新文件夹。您可以找到示例合约 Lock.sol 的所有类型和接口。
若要部署到本地网络,首先需要启动本地测试节点。
npx hardhat node
您应该会看到地址和私钥的列表。
这些是为我们创建的20个测试帐户和地址,我们可以用来部署和测试我们的智能合约。每个账户还加载了10,000个假以太币。稍后,我们将学习如何将测试帐户MetaMask导入其中,以便我们可以使用它。
现在我们可以运行部署脚本,并为我们要部署到本地网络的CLI提供一个标志。在VS Code中,打开另一个终端以运行以下命令:
npx hardhat run scripts/deploy.ts --network localhost
锁定协定部署到0x5FbDB2315678afecb367f032d93F642f64180aa3。
还行。编译并部署示例协定。这验证了我们的web3开发环境是否已设置。现在我们运行“hardhat clean”来清理示例合约并开始编写我们自己的NFT可收集智能合约。
npx hardhat clean
现在让我们安装OpenZeppelin合约包。这将使我们能够访问ERC721合约(NFT的标准)以及我们稍后会遇到的一些辅助库。
npm install @openzeppelin/contracts
从合约文件夹中删除 lock.sol。创建一个名为NFTCollectible.sol的新文件。
我们将使用Solidity v8.4。我们的合同将继承自OpenZeppelin的ERC721Enumerable和Ownable合同。前者除了在处理NFT集合时有用的一些辅助函数外,还具有ERC721(NFT)标准的默认实现。后者允许我们为合同的某些方面添加管理权限。
除此之外,我们还将使用OpenZeppelin的SafeMath和Counters库分别安全地处理无符号整数算法(通过防止溢出)和令牌ID。
这就是我们的合同的样子:
- // contracts/NFT.sol
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.9;
-
- import "@openzeppelin/contracts/utils/Counters.sol";
- import "@openzeppelin/contracts/access/Ownable.sol";
- import "@openzeppelin/contracts/utils/math/SafeMath.sol";
- import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
-
- contract NFTCollectible is ERC721Enumerable, Ownable {
- using SafeMath for uint256;
- using Counters for Counters.Counter;
-
- Counters.Counter private _tokenIds;
-
- uint256 public constant MAX_SUPPLY = 100;
- uint256 public constant PRICE = 0.01 ether;
- uint256 public constant MAX_PER_MINT = 5;
-
- string public baseTokenURI;
-
- constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
- setBaseURI(baseURI);
- }
-
- function setBaseURI(string memory _baseTokenURI) public onlyOwner {
- baseTokenURI = _baseTokenURI;
- }
-
- function _baseURI() internal view virtual override returns (string memory) {
- return baseTokenURI;
- }
-
- function reserveNFTs() public onlyOwner {
- uint256 totalMinted = _tokenIds.current();
- require(totalMinted.add(10) < MAX_SUPPLY, "Not enough NFTs");
- for (uint256 i = 0; i < 10; i++) {
- _mintSingleNFT();
- }
- }
-
- function mintNFTs(uint _count) public payable {
- uint totalMinted = _tokenIds.current();
-
- require(totalMinted.add(_count) <= MAX_SUPPLY, "Not enough NFTs left!");
- require(_count >0 && _count <= MAX_PER_MINT,
- "Cannot mint specified number of NFTs.");
- require(msg.value >= PRICE.mul(_count), "Not enough ether to purchase NFTs.");
-
- for (uint i = 0; i < _count; i++) {
- _mintSingleNFT();
- }
- }
-
- function _mintSingleNFT() private {
- uint256 newTokenID = _tokenIds.current();
- _safeMint(msg.sender, newTokenID);
- _tokenIds.increment();
- }
-
- function tokensOfOwner(address _owner) external view returns (uint[] memory) {
-
- uint tokenCount = balanceOf(_owner);
- uint[] memory tokensId = new uint256[](tokenCount);
-
- for (uint i = 0; i < tokenCount; i++) {
- tokensId[i] = tokenOfOwnerByIndex(_owner, i);
- }
- return tokensId;
- }
-
- function withdraw() public payable onlyOwner {
- uint balance = address(this).balance;
- require(balance > 0, "No ether left to withdraw");
-
- (bool success, ) = (msg.sender).call{value: balance}("");
- require(success, "Transfer failed.");
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
我们在构造函数调用中设置baseTokenURI。我们还调用父构造函数并为我们的NFT集合设置名称和符号。
我们的NFT JSON元数据可在文章开头提到的这个IPFS UR中找到。
当我们将其设置为基本UR时,OpenZeppelin的实现会自动推断每个令牌的 URI。它假定令牌1的元数据将在ipfs://QmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg/1上可用,令牌2的元数据将在ipfs://QmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg/2上可用,依此类推。
但是,我们需要告诉我们的合约,我们定义的baseTokenURI变量是合约必须使用的基本URI。为此,我们覆盖一个名为_baseURI()的空函数并使其返回baseTokenURI。
现在让我们将注意力转向主要的mint NFT功能。我们的用户和客户在想从我们的收藏中购买和mint NFT时会调用此功能。
任何人都可以通过支付一定数量的ether + gas来制造一定数量的NFT,因为他们正在向此功能发送以太币,我们必须将其标记为应付。
在允许造币厂发生之前,我们需要进行三项检查:
如果我们无法撤回已发送到合约的以太币,那么到目前为止我们付出的所有努力都将付诸东流。
让我们编写一个函数,允许我们提取合约的全部余额。这显然会被标记为onlyOwner。
首先,编译我们的新智能合约。
npx hardhat compile
编译完成后,将在 src/typechain-types 文件夹中生成新合约的类型。
您可以在 NFTCollectible__factory.ts 上找到新联系人的ABI。
删除Lock.ts并在测试文件夹中添加 NFTCollectible.ts。让我们从下面的代码开始。
- import { expect } from "chai";
- import { ethers } from "hardhat";
- import { NFTCollectible } from "../src/typechain-types/contracts/NFTCollectible";
- import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
-
- describe("NFTCollectible", function () {
- let contract : NFTCollectible;
- let owner : SignerWithAddress;
- let addr1 : SignerWithAddress;
- }
因为每个测试用例都需要部署合约。所以我们把部署写成beforeEach。
- beforeEach(async function () {
- // Get the ContractFactory and Signers here.
- [owner, addr1] = await ethers.getSigners();
- const contractFactory = await ethers.getContractFactory("NFTCollectible");
- contract = await contractFactory.deploy("baseTokenURI");
- await contract.deployed();
- }); beforeEach(async function () {
- // Get the ContractFactory and Signers here.
- [owner, addr1] = await ethers.getSigners();
- const contractFactory = await ethers.getContractFactory("NFTCollectible");
- contract = await contractFactory.deploy("baseTokenURI");
- await contract.deployed();
- });
现在我们添加事务测试用例。
- it("Reserve NFTs should 10 NFTs reserved", async function () {
- let txn = await contract.reserveNFTs();
- await txn.wait();
- expect(await contract.balanceOf(owner.address)).to.equal(10);
- });
- it("Sending 0.03 ether should mint 3 NFTs", async function () {
- let txn = await contract.mintNFTs(3,
- { value: ethers.utils.parseEther('0.03') });
- await txn.wait();
- expect(await contract.balanceOf(owner.address)).to.equal(3);
- });
- it("Withdrawal should withdraw the entire balance", async function () {
- let provider = ethers.provider
- const ethBalanceOriginal = await provider.getBalance(owner.address);
- console.log("original eth balanace %f", ethBalanceOriginal);
- let txn = await contract.connect(addr1).mintNFTs(1,
- { value: ethers.utils.parseEther('0.01') });
- await txn.wait();
-
- const ethBalanceBeforeWithdrawal = await provider.getBalance(owner.address);
- console.log("eth balanace before withdrawal %f", ethBalanceBeforeWithdrawal);
- txn = await contract.connect(owner).withdraw();
- await txn.wait();
- const ethBalanceAfterWithdrawal = await provider.getBalance(owner.address);
- console.log("eth balanace after withdrawal %f", ethBalanceAfterWithdrawal);
- expect(ethBalanceOriginal.eq(ethBalanceBeforeWithdrawal)).to.equal(true);
- expect(ethBalanceAfterWithdrawal.gt
- (ethBalanceBeforeWithdrawal)).to.equal(true);
- });
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
运行测试。
npx hardhat test
更改scripts\deplot.ts 文件中的主函数。
将lock合约部署替换为main函数中的NTFCollectible合约部署。请注意,我们需要将我们的NFT集合基本URL传递给合约的构造函数。
- const baseTokenURI = "ipfs://QmPz4f9RY2pwgiQ34UrQ8ZtLf31QTTS8FSSJ9GCWvktXtg/";
- // Get contract that we want to deploy
- const contractFactory = await ethers.getContractFactory("NFTCollectible");
- // Deploy contract with the correct constructor arguments
- const contract = await contractFactory.deploy(baseTokenURI);
-
- // Wait for this transaction to be mined
- await contract.deployed();
-
- console.log("NFTCollectible deployed to:", contract.address);
在VS Code中打开终端以运行:
npx hardhat node
打开另一个终端以运行部署命令。
npx hardhat run scripts/deploy.ts --network localhost
我们的合同被部署到地址0x5FbDB2315678afecb367f032d93F642f64180aa3。
我们已经部署了我们的合约。接下来,我将向您展示如何构建一个React客户端来使用此智能合约提供的功能。
Material UI是一个开源的React组件库,它实现了Google的 Material Design。
它包括一个全面的预构建组件集合,这些组件开箱即用,可在生产中使用。
Material UI设计精美,并具有一套自定义选项,可让您轻松地在我们的组件之上实现您自己的自定义设计系统。
安装Material UI。
npm install @mui/material @emotion/react @emotion/styled
安装Material UI图标。
npm install @mui/icons-material
MUI具有所有不同类型的组件。我们将使用App Bar, Box, Stack, Modal和Image List。
App Bar显示与当前屏幕相关的信息和操作。
Box组件充当大多数CSS实用程序需求的包装器组件。
Stack组件管理沿垂直或水平轴的直接子项的布局,每个子项之间具有可选的间距和/或分隔符。
Modal组件为创建对话框、弹出框、lightbox或其他任何内容提供了坚实的基础。
图像列表在有组织的网格中显示图像集合。
目前,tsconfig.json是由hardhat typescript创建的。它没有完全覆盖React Typescript。更新 tsconfig.json,如下所示:
- {
- "compilerOptions": {
- "target": "es2021",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "commonjs",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "outDir": "dist",
- "sourceMap": true,
- "jsx": "react-jsx"
- },
- "include": ["./scripts", "./test", "./src/typechain-types"],
- "files": ["./hardhat.config.ts"]
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
下载MetaMask.svg到src文件夹,我们将其用作logo。
在 src 文件夹下添加 Demo.tsx 文件,然后复制以下代码。我们从基本应用栏开始。
- import * as React from 'react';
- import AppBar from '@mui/material/AppBar';
- import Toolbar from '@mui/material/Toolbar';
- import Typography from '@mui/material/Typography';
- import CssBaseline from '@mui/material/CssBaseline';
- import Container from '@mui/material/Container';
- import Stack from '@mui/material/Stack';
- import Avatar from '@mui/material/Avatar';
- import logo from './metamask.svg';
-
- function Demo() {
- return (
- <React.Fragment>
- <CssBaseline />
- <AppBar>
- <Toolbar>
- <Stack direction="row" spacing={2}>
- <Typography variant="h3" component="div">
- NFT Collection
- </Typography>
- <Avatar alt="logo" src={logo} sx={{ width: 64, height: 64 }} />
- </Stack>
- </Toolbar>
- </AppBar>
- <Toolbar />
- <Container>
- </Container>
- </React.Fragment>
- );
- }
-
- export default Demo;
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
然后更改 index.tsx 以加载演示而不是应用程序。
- import React from 'react';
- import ReactDOM from 'react-dom/client';
- import './index.css';
- import App from './App';
- import Demo from './Demo';
- import reportWebVitals from './reportWebVitals';
-
- const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement
- );
- root.render(
- <React.StrictMode>
- <Demo />
- </React.StrictMode>
- );
-
- // If you want to start measuring performance in your app, pass a function
- // to log results (for example: reportWebVitals(console.log))
- // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
- reportWebVitals();
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
运行它。
npm start
让我们仔细检查我们的NFT集合合约部署的地址。它是0x5FbDB2315678afecb367f032d93F642f64180aa3。定义第一个const。
const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
然后定义IWallet接口。
- interface IWallet {
- iconColor: string;
- connectedWallet: string;
- contractAddress: string;
- contractSymbol: string;
- contractBaseTokenURI: string;
- contractOwnerAddress: string;
- contractPrice: string;
- isOwner: boolean;
- }
我们需要使用useState hook来初始化和更新IWallet实例。React从16.8中引入了Hooks。useState是一个内置钩子,允许您在函数组件中使用本地状态。将初始状态传递给此函数,它将返回一个具有当前状态值(不一定是初始状态)的变量和另一个函数来更新此值。
- const [state, setState] = React.useState<IWallet>({
- iconColor: "disabled",
- connectedWallet: "",
- contractSymbol: "",
- contractAddress: "",
- contractBaseTokenURI: "",
- contractOwnerAddress: "",
- contractPrice: "",
- isOwner: false
- });
导入ethers和NFTCollectible__factory。
- import { BigNumber, ethers } from "ethers";
- import { NFTCollectible__factory } from
- './typechain-types/factories/contracts/NFTCollectible__factory'
现在写连接钱包功能。
- const connectWallet = async () => {
- try {
- console.log("connect wallet");
- const { ethereum } = window;
-
- if (!ethereum) {
- alert("Please install MetaMask!");
- return;
- }
-
- const accounts = await ethereum.request({
- method: "eth_requestAccounts",
- });
- console.log("Connected", accounts[0]);
-
- const provider = new ethers.providers.Web3Provider(ethereum);
- const contract = NFTCollectible__factory.connect
- (contractAddress, provider.getSigner());
- //const contract = new ethers.Contract
- //(contractAddress, NFTCollectible__factory.abi, signer) as NFTCollectible;
- const ownerAddress = await contract.owner();
- const symbol = await contract.symbol();
- const baseTokenURI = await contract.baseTokenURI();
- const balance = await (await contract.balanceOf(accounts[0])).toNumber();
- const ethBalance = ethers.utils.formatEther
- (await provider.getBalance(accounts[0]));
- const isOwner = (ownerAddress.toLowerCase() === accounts[0].toLowerCase());
- const price = ethers.utils.formatEther(await contract.PRICE());
- setState({
- iconColor: "success",
- connectedWallet: accounts[0],
- contractSymbol: symbol,
- contractAddress: contract.address,
- contractBaseTokenURI: baseTokenURI,
- contractOwnerAddress: ownerAddress,
- contractPrice: `${price} ETH`,
- isOwner: isOwner
- });
-
- console.log("Connected", accounts[0]);
- } catch (error) {
- console.log(error);
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
你可以在这里看到,必须安装MetaMask扩展,否则你无法从窗口中获取Ethereum对象。进行UI更改,添加“连接”按钮和“已连接帐户”、“合同地址”、“合同基础令牌URI”文本字段。此外,使用Account Circle图标指示是否已连接。
- <Stack direction="row" spacing={2} sx={{ margin: 5 }}>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <AccountCircle color={state.iconColor} sx={{ mr: 1, my: 0.5 }} />
- <TextField id="wallet_address" label="Connected Account"
- sx={{ width: 300 }} variant="standard" value={state.connectedWallet}
- inputProps={{ readOnly: true, }}
- />
- </Box>
- <TextField id="contract_symbol" label="Contract Symbol"
- vari-ant="standard" value={state.contractSymbol}
- inputProps={{ readOnly: true, }}
- />
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="contract_address" label="Contract Address"
- sx={{ width: 400 }} variant="standard" value={state.contractAddress}
- inputProps={{ readOnly: true, }}
- />
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="contract_baseURI" label="Contract Base Token URI"
- sx={{ width: 500 }} variant="standard" value={state.contractBaseTokenURI}
- inputProps={{ readOnly: true, }}
- />
- </Box>
- </Stack>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
以下是使用Stack的外观。堆栈有两个方向,“row”和“column”。
运行我们的应用。
单击连接按钮。
呵呵,工作一样迷人!
为图像URL集合添加状态hook。
const [nftCollection, setNFTCollection] = React.useState<string[]>([]);
写入加载NFT集合函数。
- const loadNFTCollection = async () => {
- try {
- console.log("load NFT collection");
- let baseURI: string = state.contractBaseTokenURI;
- baseURI = baseURI.replace("ipfs://", "https://gateway.pinata.cloud/ipfs/");
- setNFTCollection(
- [
- `${baseURI}0001.svg`,
- `${baseURI}0002.svg`,
- `${baseURI}0003.svg`,
- `${baseURI}0004.svg`,
- ]);
- } catch (error) {
- console.log(error);
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
导入ImageList和ImageListItem。
- import ImageList from '@mui/material/ImageList';
- import ImageListItem from '@mui/material/ImageListItem';
将图像列表与NFT URL集合绑定。
- <ImageList sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}>
- {nftCollection.map((item) => (
- <ImageListItem key={item}>
- <img
- src={`${item}?w=164&h=164&fit=crop&auto=format`}
- srcSet={`${item}?w=164&h=164&fit=crop&auto=format&dpr=2 2x`}
- loading="lazy"
- />
- </ImageListItem>
- ))}
- </ImageList>
再一次运行应用程序并单击“加载NFT集合”按钮。
添加IService接口。
- interface IService {
- account: string;
- ethProvider?: ethers.providers.Web3Provider,
- contract?: NFTCollectible;
- currentBalance: number;
- ethBalance: string;
- mintAmount: number;
- }
使用状态挂钩。
- const [service, setService] = React.useState<IService>({
- account: "",
- currentBalance: 0,
- ethBalance: "",
- mintAmount: 0
- });
Mint NFT功能。
- const mintNFTs = async () => {
- try {
- console.log("mint NFTs");
- const address = service.account;
- const amount = service.mintAmount!;
- const contract = service.contract!;
- const price = await contract.PRICE();
- const ethValue = price.mul(BigNumber.from(amount));
- const signer = service.ethProvider!.getSigner();
- let txn = await contract.connect(signer!).mintNFTs(amount, { value: ethValue });
- await txn.wait();
- const balance = await contract.balanceOf(address);
- setService({...service, currentBalance: balance.toNumber(), mintAmount: 0});
- } catch (error) {
- console.log(error);
- }
- };
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Mint模态对话框。
- <Modal
- aria-labelledby="transition-modal-title"
- aria-describedby="transition-modal-description"
- open={open}
- onClose={handleClose}
- closeAfterTransition
- BackdropComponent={Backdrop}
- BackdropProps={{
- timeout: 500,
- }}
- >
- <Fade in={open}>
- <Box sx={modalStyle}>
- <Stack spacing={1} sx={{ width: 500 }}>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="mint_account" label="Account"
- sx={{ width: 500 }} variant="standard" value={service.account}
- inputProps={{ readOnly: true}}
- />
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="price" label="NFT Price"
- sx={{ width: 500 }} variant="standard" value={state.contractPrice}
- inputProps={{ readOnly: true}}
- />
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="balance" label="Balance"
- sx={{ width: 500 }} variant="standard" value={service.currentBalance}
- type = "number" inputProps={{ readOnly: true}}
- />
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="mint_amount" type="number"
- label="Mint Amount" sx={{ width: 500 }}
- variant="standard" value={service.mintAmount}
- onChange={event => {
- const { value } = event.target;
- const amount = parseInt(value);
- setService({...service, mintAmount: amount});
- }}
- />
- </Box>
- <Stack direction="row" spacing={2} sx={{ margin: 5 }}>
- <Button variant="outlined" onClick={mintNFTs}>Mint</Button>
- <Button variant="outlined" onClick={handleClose}>close</Button>
- </Stack>
- </Stack>
- </Box>
- </Fade>
- </Modal>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
运行应用程序,单击“Mint NFT”按钮。您将看到一个弹出对话框。
Withdraw功能。
- const withdraw = async () => {
- try {
- console.log("owner withdraw");
- const contract = service.contract!;
- const provider = service.ethProvider!;
- let txn = await contract.withdraw();
- await txn.wait();
- const ethBalance = ethers.utils.formatEther
- (await provider!.getBalance(service.account));
- setService({...service, ethBalance: `${ethBalance} ETH`});
- } catch (error) {
- console.log(error);
- }
- };
Withdraw模态对话,
- <Modal
- id="withdrawal_modal"
- aria-labelledby="transition-modal-title"
- aria-describedby="transition-modal-description"
- open={openWithdrawal}
- onClose={handleCloseWithdrawal}
- closeAfterTransition
- BackdropComponent={Backdrop}
- BackdropProps={{
- timeout: 500,
- }}
- >
- <Fade in={openWithdrawal}>
- <Box sx={modalStyle}>
- <Stack spacing={1} sx={{ width: 500 }}>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="owner_account" label="Owner Account"
- sx={{ width: 500 }} variant="standard" value={service.account}
- inputProps={{ readOnly: true }}
- />
- </Box>
- <Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
- <TextField id="ethbalance" label="ETH Balance"
- sx={{ width: 500 }} variant="standard" value={service.ethBalance}
- inputProps={{ readOnly: true }}
- />
- </Box>
- <Stack direction="row" spacing={2} sx={{ margin: 5 }}>
- <Button variant="outlined" onClick={withdraw}>Withdraw</Button>
- <Button variant="outlined" onClick={handleCloseWithdrawal}>close</Button>
- </Stack>
- </Stack>
- </Box>
- </Fade>
- </Modal>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Withdraw仅适用于部署合同的合同所有者。因此,提取按钮仅对所有者启用。
- <Button variant="contained" disabled={!state.isOwner}
- onClick={handleOpenWithdrawal}>Withdraw</Button>
如果MetaMask当前连接的帐户不是合约的所有者,则“提取”按钮将被禁用。
在MetaMask中将帐户更改为所有者。
更改为所有者后,单击我们反应客户端中的连接按钮,提取按钮将被启用。
点击“提取”按钮。
就是这样。一个小而有趣的web3应用程序完成了。现在你打开了一扇通往全新世界的大门。
我们已经完成了react web3客户端的构建。有一个非常重要的反应概念我没有提到。那是React Router。在我们的应用程序中,我创建了一个演示组件。并直接放到index.tsx中。对于简单的应用程序来说,这不是问题。但是,如果您有多个组件想要导航怎么办?React Router提供完美的解决方案。
React Router不仅仅是将URL与函数或组件匹配:它是关于构建映射到URL的完整用户界面,因此它可能包含比您习惯的更多概念。React router做以下三个主要工作。
安装Ract Router。最新版本是V6。
npm install react-router-dom
现在在App.tsx中导入react router和演示组件。
- import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
-
- import Link from '@mui/material/Link';
-
- import Demo from './Demo'
在App.tsx中创建Home函数。使用Link让用户更改URL或useNavigate自己更改。这里我们使用的Link来自Material UI而不是来自react-router-dom。本质上,除了样式之外,它们是一回事。
- function Home() {
- return (
- <div className="App">
- <header className="App-header">
- <img src={logo} className="App-logo" alt="logo" />
- <p>
- With cryptocurrencies and blockchain technology,
- NFTs have become part of this crazy world.
- </p>
- <Box
- sx={{
- display: 'flex', flexWrap: 'wrap',
- justifyContent: 'center', typography: 'h3',
- '& > :not(style) + :not(style)': {
- ml: 2,
- },
- }}>
- <Link href="/demo" underline="none" sx={{ color: '#FFF' }}>
- Web3 NFT Demo
- </Link>
- </Box>
- </header>
- </div>
- );
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
在App函数中配置路由。
- function App() {
- return (
- <Router>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="/demo" element={<Demo />} />
- </Routes>
- </Router>
- );
- }
在React Router以前的版本中,您必须以某种方式对路由进行排序,以便在多个路由与不明确的URL匹配时获得正确的路由进行呈现。V6要聪明得多,并且会选择最具体的匹配项,因此您不必再担心这一点。
不要忘记最后一件事,改回index.tsx中的App组件。
- root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>
- );
现在运行我们的应用程序。
npm start
单击 Wbe3 NFT演示,它将导航到我们的web3组件。
首先,在您的浏览器(Chrome,Firefox或Edge)上安装MetaMask扩展程序。
然后下载并提取源代码,使用Visual Studio Code打开react-web3-ts文件夹。
然后在VS Code中打开新终端。
npm install
npx hardhat compile
npx hardhat node
npx hardhat run scripts/deploy.ts --network localhost
npm start
我们在这里涵盖了很多内容,从智能合约到web3应用程序。我希望你学到了很多东西。现在你可能会问,“智能合约有什么用?“
智能合约相对于集中式系统的优势:
示例项目现在在github中,dapp-ts。
https://www.codeproject.com/Articles/5338801/Build-NFT-Collection-Web3-Application-with-Hardha
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。