# Alchemy的the Road to Web3第七周文本教程- 从零开始构建 NFT 市场

By [KnowYourself](https://paragraph.com/@knowyourself-2) · 2022-09-25

---

### 准备工作

*   注册一个 Alchemy 帐户并创建一个新应用程序
    
*   MetaMask切换到Goerli且你的钱包里面至少有 0.1 Goerli ETH
    
*   如果您没有 Goerli 地址，[将 MetaMask 连接到 Goerli 网络](https://docs.alchemy.com/docs/how-to-add-alchemy-rpc-endpoints-to-metamask)， 接着[使用 Goerli 水龙头请求 Goerli ETH](https://goerlifaucet.com/). 您将需要 Goerli ETH 来部署智能合约并将 NFT 上传到您的 NFT 市场。
    

在MetaMask中添加下面的链信息：

*   **Network Name:** Goerli Test Network
    
    **RPC base URL:** [https://eth-goerli.alchemyapi.io/v2/{INSERT](https://eth-goerli.alchemyapi.io/v2/%7BINSERT) YOUR API KEY}
    
    **Chain ID:** 5
    
    **Block Explorer URL:** [https://goerli.etherscan.io/](https://goerli.etherscan.io/)
    

1.设置存储库、设置环境变量和 Hardhat 配置
--------------------------

本次课程我们继续使用repli来做

首先我们将本次需要的前端代码clone下来，具体操作如下：

![导入仓库文件](https://storage.googleapis.com/papyrus_images/423f9b0236b80e7c3cb91f0f4ce6f6863193240406d0cf52ade6d2bc73727052.png)

导入仓库文件

导入仓库文件

导入仓库文件，等到项目倒入完成，当项目完成后，我们需要去修改配置文件

![导入文件](https://storage.googleapis.com/papyrus_images/0a936ed20097f47f59f6fbf6f0ffe9018d450240bc8205933f8761063c07a55b.png)

导入文件

导入文件

导入文件

然后在项目下执行

    npm install
    npm start
    

首先我们将hardhat.config.js的内容修改如下：

    require("@nomiclabs/hardhat-waffle");
    require("@nomiclabs/hardhat-ethers");
    const fs = require('fs');
    // const infuraId = fs.readFileSync(".infuraid").toString().trim() || "";
    require('dotenv').config();
    
    task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
      const accounts = await hre.ethers.getSigners();
    
      for (const account of accounts) {
        console.log(account.address);
      }
    });
    
    module.exports = {
      defaultNetwork: "hardhat",
      networks: {
        hardhat: {
          chainId: 1337
        },
        goerli: {
          url: process.env.REACT_APP_ALCHEMY_API_URL,
          accounts: [ process.env.REACT_APP_PRIVATE_KEY ]
        }
      },
      solidity: {
        version: "0.8.4",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200
          }
        }
      }
    };
    

同时新建一个.env文件，文件容易如下，如果无法新建直接将该值替换即可

    REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
    REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
    

![](https://storage.googleapis.com/papyrus_images/dc6cac874e21c061dd91f74071fb8a13a6590ebfd641492b163ea4c7eb5a64fb.png)

当我们上面做好了，我们在shell中输入一下命令，让系统帮助我们安装依赖等信息：

    npm install dotenv --save
    

2.使用 Piñata 将数据上传到 IPFS
-----------------------

如果没有 Piñata 帐户，[注册](https://pinata.cloud/signup)一个即可。当注册登录进去后我们需要去获取api\_key.

![](https://storage.googleapis.com/papyrus_images/bb06c581344736dae382c7c54ccc79625a14bbebeb58b9416f9fd4702b1dfb02.png)

我们需要新建一个key，同时将Admin权限开启，给自己的key命名

![](https://storage.googleapis.com/papyrus_images/a62c9b654b32b58d212263280bee6ff0b569b8b166e45aeefee152a75ea764c7.png)

当我们新建成功后，页面会提示一个有关key的信息，将它复制到安全的地方

![](https://storage.googleapis.com/papyrus_images/f9e2c8a9eea9f0aa1f597f03f6aad5bd7827724c2b741d3f2d0fe430d2cbb7d6.png)

同时将我们的.env文件进行修改：

    REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
    REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
    REACT_APP_PINATA_KEY="<YOUR_PINATA_KEY>"
    REACT_APP_PINATA_SECRET="<YOUR_PINATA_SECRET>"
    

3.编写合约
------

现在去修改NFTMarketplace.sol这个文件，合约代码如下：

    //SPDX-License-Identifier: Unlicense
    pragma solidity ^0.8.0;
    
    import "hardhat/console.sol";
    import "@openzeppelin/contracts/utils/Counters.sol";
    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    
    contract NFTMarketplace is ERC721URIStorage {
    
        using Counters for Counters.Counter;
        //_tokenIds variable has the most recent minted tokenId
        Counters.Counter private _tokenIds;
        //Keeps track of the number of items sold on the marketplace
        Counters.Counter private _itemsSold;
        //owner is the contract address that created the smart contract
        address payable owner;
        //The fee charged by the marketplace to be allowed to list an NFT
        uint256 listPrice = 0.01 ether;
    
        //The structure to store info about a listed token
        struct ListedToken {
            uint256 tokenId;
            address payable owner;
            address payable seller;
            uint256 price;
            bool currentlyListed;
        }
    
        //the event emitted when a token is successfully listed
        event TokenListedSuccess (
            uint256 indexed tokenId,
            address owner,
            address seller,
            uint256 price,
            bool currentlyListed
        );
    
        //This mapping maps tokenId to token info and is helpful when retrieving details about a tokenId
        mapping(uint256 => ListedToken) private idToListedToken;
    
        constructor() ERC721("NFTMarketplace", "NFTM") {
            owner = payable(msg.sender);
        }
    
        function updateListPrice(uint256 _listPrice) public payable {
            require(owner == msg.sender, "Only owner can update listing price");
            listPrice = _listPrice;
        }
    
        function getListPrice() public view returns (uint256) {
            return listPrice;
        }
    
        function getLatestIdToListedToken() public view returns (ListedToken memory) {
            uint256 currentTokenId = _tokenIds.current();
            return idToListedToken[currentTokenId];
        }
    
        function getListedTokenForId(uint256 tokenId) public view returns (ListedToken memory) {
            return idToListedToken[tokenId];
        }
    
        function getCurrentToken() public view returns (uint256) {
            return _tokenIds.current();
        }
    
        //The first time a token is created, it is listed here
        function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
            //Increment the tokenId counter, which is keeping track of the number of minted NFTs
            _tokenIds.increment();
            uint256 newTokenId = _tokenIds.current();
    
            //Mint the NFT with tokenId newTokenId to the address who called createToken
            _safeMint(msg.sender, newTokenId);
    
            //Map the tokenId to the tokenURI (which is an IPFS URL with the NFT metadata)
            _setTokenURI(newTokenId, tokenURI);
    
            //Helper function to update Global variables and emit an event
            createListedToken(newTokenId, price);
    
            return newTokenId;
        }
    
        function createListedToken(uint256 tokenId, uint256 price) private {
            //Make sure the sender sent enough ETH to pay for listing
            require(msg.value == listPrice, "Hopefully sending the correct price");
            //Just sanity check
            require(price > 0, "Make sure the price isn't negative");
    
            //Update the mapping of tokenId's to Token details, useful for retrieval functions
            idToListedToken[tokenId] = ListedToken(
                tokenId,
                payable(address(this)),
                payable(msg.sender),
                price,
                true
            );
    
            _transfer(msg.sender, address(this), tokenId);
            //Emit the event for successful transfer. The frontend parses this message and updates the end user
            emit TokenListedSuccess(
                tokenId,
                address(this),
                msg.sender,
                price,
                true
            );
        }
        
        //This will return all the NFTs currently listed to be sold on the marketplace
        function getAllNFTs() public view returns (ListedToken[] memory) {
            uint nftCount = _tokenIds.current();
            ListedToken[] memory tokens = new ListedTokenUnsupported embed;
            uint currentIndex = 0;
    
            //at the moment currentlyListed is true for all, if it becomes false in the future we will 
            //filter out currentlyListed == false over here
            for(uint i=0;i<nftCount;i++)
            {
                uint currentId = i + 1;
                ListedToken storage currentItem = idToListedToken[currentId];
                tokens[currentIndex] = currentItem;
                currentIndex += 1;
            }
            //the array 'tokens' has the list of all NFTs in the marketplace
            return tokens;
        }
        
        //Returns all the NFTs that the current user is owner or seller in
        function getMyNFTs() public view returns (ListedToken[] memory) {
            uint totalItemCount = _tokenIds.current();
            uint itemCount = 0;
            uint currentIndex = 0;
            
            //Important to get a count of all the NFTs that belong to the user before we can make an array for them
            for(uint i=0; i < totalItemCount; i++)
            {
                if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender){
                    itemCount += 1;
                }
            }
    
            //Once you have the count of relevant NFTs, create an array then store all the NFTs in it
            ListedToken[] memory items = new ListedTokenUnsupported embed;
            for(uint i=0; i < totalItemCount; i++) {
                if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender) {
                    uint currentId = i+1;
                    ListedToken storage currentItem = idToListedToken[currentId];
                    items[currentIndex] = currentItem;
                    currentIndex += 1;
                }
            }
            return items;
        }
    
        function executeSale(uint256 tokenId) public payable {
            uint price = idToListedToken[tokenId].price;
            address seller = idToListedToken[tokenId].seller;
            require(msg.value == price, "Please submit the asking price in order to complete the purchase");
    
            //update the details of the token
            idToListedToken[tokenId].currentlyListed = true;
            idToListedToken[tokenId].seller = payable(msg.sender);
            _itemsSold.increment();
    
            //Actually transfer the token to the new owner
            _transfer(address(this), msg.sender, tokenId);
            //approve the marketplace to sell NFTs on your behalf
            approve(address(this), tokenId);
    
            //Transfer the listing fee to the marketplace creator
            payable(owner).transfer(listPrice);
            //Transfer the proceeds from the sale to the seller of the NFT
            payable(seller).transfer(msg.value);
        }
    
        //We might add a resell token function in the future
        //In that case, tokens won't be listed by default but users can send a request to actually list a token
        //Currently NFTs are listed by default
    }
    

![](https://storage.googleapis.com/papyrus_images/5b9c0529d85cc501b5737fd8e7502eb17b95bd43f8eaea3730a6cc665026c65e.png)

4.在 Goerli 上部署智能合约
------------------

找到部署合约的deploy.js，将下面的代码写入：

    const { ethers } = require("hardhat");
    const hre = require("hardhat");
    const fs = require("fs");
    
    async function main() {
      //get the signer that we will use to deploy
      const [deployer] = await ethers.getSigners();
      
      //Get the NFTMarketplace smart contract object and deploy it
      const Marketplace = await hre.ethers.getContractFactory("NFTMarketplace");
      const marketplace = await Marketplace.deploy();
    
      await marketplace.deployed();
      
      //Pull the address and ABI out while you deploy, since that will be key in interacting with the smart contract later
      const data = {
        address: marketplace.address,
        abi: JSON.parse(marketplace.interface.format('json'))
      }
    
      //This writes the ABI and address to the marketplace.json
      //This data is then used by frontend files to connect with the smart contract
      fs.writeFileSync('./src/Marketplace.json', JSON.stringify(data))
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    

![](https://storage.googleapis.com/papyrus_images/c06070c784c0a44a0fd672d7feb9c214edc20c40daaabc7bc73fd6a963a9176b.png)

![](https://storage.googleapis.com/papyrus_images/2dbd348f157e903e7ac0908ff8ea15050cf411d5e2b429cee9262963353136f0.png)

使用下面的命令部署合约

    npx hardhat run --network goerli scripts/deploy.js
    

当你看到生成了一下的文件则说明你已经成功部署了

![](https://storage.googleapis.com/papyrus_images/fbc94063be713bf3bdf2dbf38d964a5adbe8b4540492fe8603decc8d5a32ae41.png)

我们在部署的json文件中也可以找到合约的地址，我们去浏览器查看下合约地址是：

0x75E88d1B05014A5B8B24EfA73e3A740ae050063a

5.添加将 NFT 元数据上传到 Piñata 的功能
---------------------------

我们首先新建一个pinata.js并将下面的代码填写进去，记住如果是硬编码，需要替换你的key等信息：

    //require('dotenv').config();
    const key = process.env.REACT_APP_PINATA_KEY;
    const secret = process.env.REACT_APP_PINATA_SECRET;
    
    const axios = require('axios');
    const FormData = require('form-data');
    
    export const uploadJSONToIPFS = async(JSONBody) => {
        const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
        //making axios POST request to Pinata ⬇️
        return axios 
            .post(url, JSONBody, {
                headers: {
                    pinata_api_key: key,
                    pinata_secret_api_key: secret,
                }
            })
            .then(function (response) {
               return {
                   success: true,
                   pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
               };
            })
            .catch(function (error) {
                console.log(error)
                return {
                    success: false,
                    message: error.message,
                }
    
        });
    };
    
    export const uploadFileToIPFS = async(file) => {
        const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
        //making axios POST request to Pinata ⬇️
        
        let data = new FormData();
        data.append('file', file);
    
        const metadata = JSON.stringify({
            name: 'testname',
            keyvalues: {
                exampleKey: 'exampleValue'
            }
        });
        data.append('pinataMetadata', metadata);
    
        //pinataOptions are optional
        const pinataOptions = JSON.stringify({
            cidVersion: 0,
            customPinPolicy: {
                regions: [
                    {
                        id: 'FRA1',
                        desiredReplicationCount: 1
                    },
                    {
                        id: 'NYC1',
                        desiredReplicationCount: 2
                    }
                ]
            }
        });
        data.append('pinataOptions', pinataOptions);
    
        return axios 
            .post(url, data, {
                maxBodyLength: 'Infinity',
                headers: {
                    'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
                    pinata_api_key: key,
                    pinata_secret_api_key: secret,
                }
            })
            .then(function (response) {
                console.log("image uploaded", response.data.IpfsHash)
                return {
                   success: true,
                   pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
               };
            })
            .catch(function (error) {
                console.log(error)
                return {
                    success: false,
                    message: error.message,
                }
    
        });
    };
    

![](https://storage.googleapis.com/papyrus_images/804136a193e9608445cb8fdb8f6bbcb4a00b0b7f114676cc0264efe2c3a88f86.png)

6.将前端与智能合约集成
------------

为了使平台无缝工作，将前端与智能合约中的功能集成，需要将下面的代码直接覆盖即可

src/components/SellNFT.js：

    import Navbar from "./Navbar";
    import { useState } from "react";
    import { uploadFileToIPFS, uploadJSONToIPFS } from "../pinata";
    import Marketplace from '../Marketplace.json';
    import { useLocation } from "react-router";
    
    export default function SellNFT() {
      const [formParams, updateFormParams] = useState({ name: '', description: '', price: '' });
      const [fileURL, setFileURL] = useState(null);
      const ethers = require("ethers");
      const [message, updateMessage] = useState('');
      const location = useLocation();
      //This function uploads the NFT image to IPFS
      async function OnChangeFile(e) {
        var file = e.target.files[0];
        //check for file extension
        try {
          //upload the file to IPFS
          const response = await uploadFileToIPFS(file);
          if (response.success === true) {
            console.log("Uploaded image to Pinata: ", response.pinataURL)
            setFileURL(response.pinataURL);
          }
        }
        catch (e) {
          console.log("Error during file upload", e);
        }
      }
    
      //This function uploads the metadata to IPDS
      async function uploadMetadataToIPFS() {
        const { name, description, price } = formParams;
        //Make sure that none of the fields are empty
        if (!name || !description || !price || !fileURL)
          return;
    
        const nftJSON = {
          name, description, price, image: fileURL
        }
    
        try {
          //upload the metadata JSON to IPFS
          const response = await uploadJSONToIPFS(nftJSON);
          if (response.success === true) {
            console.log("Uploaded JSON to Pinata: ", response)
            return response.pinataURL;
          }
        }
        catch (e) {
          console.log("error uploading JSON metadata:", e)
        }
      }
    
      async function listNFT(e) {
        e.preventDefault();
    
        //Upload data to IPFS
        try {
          const metadataURL = await uploadMetadataToIPFS();
          //After adding your Hardhat network to your metamask, this code will get providers and signers
          const provider = new ethers.providers.Web3Provider(window.ethereum);
          const signer = provider.getSigner();
          updateMessage("Please wait.. uploading (upto 5 mins)")
    
          //Pull the deployed contract instance
          let contract = new ethers.Contract(Marketplace.address, Marketplace.abi, signer)
    
          //massage the params to be sent to the create NFT request
          const price = ethers.utils.parseUnits(formParams.price, 'ether')
          let listingPrice = await contract.getListPrice()
          listingPrice = listingPrice.toString()
    
          //actually create the NFT
          let transaction = await contract.createToken(metadataURL, price, { value: listingPrice })
          await transaction.wait()
    
          alert("Successfully listed your NFT!");
          updateMessage("");
          updateFormParams({ name: '', description: '', price: '' });
          window.location.replace("/")
        }
        catch (e) {
          alert("Upload error" + e)
        }
      }
    
      return (
        <div className="">
          <Navbar></Navbar>
          <div className="flex flex-col place-items-center mt-10" id="nftForm">
            <form className="bg-white shadow-md rounded px-8 pt-4 pb-8 mb-4">
              <h3 className="text-center font-bold text-purple-500 mb-8">Upload your NFT to the marketplace</h3>
              <div className="mb-4">
                <label className="block text-purple-500 text-sm font-bold mb-2" htmlFor="name">NFT Name</label>
                <input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="name" type="text" placeholder="Axie#4563" onChange={e => updateFormParams({ ...formParams, name: e.target.value })} value={formParams.name}></input>
              </div>
              <div className="mb-6">
                <label className="block text-purple-500 text-sm font-bold mb-2" htmlFor="description">NFT Description</label>
                <textarea className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" cols="40" rows="5" id="description" type="text" placeholder="Axie Infinity Collection" value={formParams.description} onChange={e => updateFormParams({ ...formParams, description: e.target.value })}></textarea>
              </div>
              <div className="mb-6">
                <label className="block text-purple-500 text-sm font-bold mb-2" htmlFor="price">Price (in ETH)</label>
                <input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" type="number" placeholder="Min 0.01 ETH" step="0.01" value={formParams.price} onChange={e => updateFormParams({ ...formParams, price: e.target.value })}></input>
              </div>
              <div>
                <label className="block text-purple-500 text-sm font-bold mb-2" htmlFor="image">Upload Image</label>
                <input type={"file"} onChange={""}></input>
              </div>
              <br></br>
              <div className="text-green text-center">{message}</div>
              <button onClick={""} className="font-bold mt-10 w-full bg-purple-500 text-white rounded p-2 shadow-lg">
                List NFT
              </button>
            </form>
          </div>
        </div>
      )
    }
    

src/components/Marketplace.js：

    import Navbar from "./Navbar";
    import NFTTile from "./NFTTile";
    import MarketplaceJSON from "../Marketplace.json";
    import axios from "axios";
    import { useState } from "react";
    
    export default function Marketplace() {
      const sampleData = [
        {
          "name": "NFT#1",
          "description": "Alchemy's First NFT",
          "website": "http://axieinfinity.io",
          "image": "https://gateway.pinata.cloud/ipfs/QmTsRJX7r5gyubjkdmzFrKQhHv74p5wT9LdeF1m3RTqrE5",
          "price": "0.03ETH",
          "currentlySelling": "True",
          "address": "0xe81Bf5A757CB4f7F82a2F23b1e59bE45c33c5b13",
        },
        {
          "name": "NFT#2",
          "description": "Alchemy's Second NFT",
          "website": "http://axieinfinity.io",
          "image": "https://gateway.pinata.cloud/ipfs/QmdhoL9K8my2vi3fej97foiqGmJ389SMs55oC5EdkrxF2M",
          "price": "0.03ETH",
          "currentlySelling": "True",
          "address": "0xe81Bf5A757C4f7F82a2F23b1e59bE45c33c5b13",
        },
        {
          "name": "NFT#3",
          "description": "Alchemy's Third NFT",
          "website": "http://axieinfinity.io",
          "image": "https://gateway.pinata.cloud/ipfs/QmTsRJX7r5gyubjkdmzFrKQhHv74p5wT9LdeF1m3RTqrE5",
          "price": "0.03ETH",
          "currentlySelling": "True",
          "address": "0xe81Bf5A757C4f7F82a2F23b1e59bE45c33c5b13",
        },
      ];
      const [data, updateData] = useState(sampleData);
      const [dataFetched, updateFetched] = useState(false);
    async function getAllNFTs() {
        const ethers = require("ethers");
        //After adding your Hardhat network to your metamask, this code will get providers and signers
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const signer = provider.getSigner();
        //Pull the deployed contract instance
        let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
        //create an NFT Token
        let transaction = await contract.getAllNFTs()
    
        //Fetch all the details of every NFT from the contract and display
        const items = await Promise.all(transaction.map(async i => {
            const tokenURI = await contract.tokenURI(i.tokenId);
            let meta = await axios.get(tokenURI);
            meta = meta.data;
    
            let price = ethers.utils.formatUnits(i.price.toString(), 'ether');
            let item = {
                price,
                tokenId: i.tokenId.toNumber(),
                seller: i.seller,
                owner: i.owner,
                image: meta.image,
                name: meta.name,
                description: meta.description,
            }
            return item;
        }))
    
        updateFetched(true);
        updateData(items);
    }
    
    if(!dataFetched)
        getAllNFTs();
    
      return (
        <div>
          <Navbar></Navbar>
          <div className="flex flex-col place-items-center mt-20">
            <div className="md:text-xl font-bold text-white">
              Top NFTs
            </div>
            <div className="flex mt-5 justify-between flex-wrap max-w-screen-xl text-center">
              {data.map((value, index) => {
                return <NFTTile data={value} key={index}></NFTTile>;
              })}
            </div>
          </div>
        </div>
      );
    
    }
    

#### src/components/Profile.js：

    import Navbar from "./Navbar";
    import { useLocation, useParams } from 'react-router-dom';
    import MarketplaceJSON from "../Marketplace.json";
    import axios from "axios";
    import { useState } from "react";
    import NFTTile from "./NFTTile";
    
    export default function Profile() {
      const [data, updateData] = useState([]);
      const [address, updateAddress] = useState("0x");
      const [totalPrice, updateTotalPrice] = useState("0");
      const [dataFetched, updateFetched] = useState(false);
      async function getNFTData(tokenId) {
        const ethers = require("ethers");
        let sumPrice = 0;
    
        //After adding your Hardhat network to your metamask, this code will get providers and signers
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const signer = provider.getSigner();
        const addr = await signer.getAddress();
    
        //Pull the deployed contract instance
        let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
    
        //create an NFT Token
        let transaction = await contract.getMyNFTs()
    
        /*
        * Below function takes the metadata from tokenURI and the data returned by getMyNFTs() contract function
        * and creates an object of information that is to be displayed
        */
    
        const items = await Promise.all(transaction.map(async i => {
          const tokenURI = await contract.tokenURI(i.tokenId);
          let meta = await axios.get(tokenURI);
          meta = meta.data;
    
          let price = ethers.utils.formatUnits(i.price.toString(), 'ether');
          let item = {
            price,
            tokenId: i.tokenId.toNumber(),
            seller: i.seller,
            owner: i.owner,
            image: meta.image,
            name: meta.name,
            description: meta.description,
          }
          sumPrice += Number(price);
          return item;
        }))
    
        updateData(items);
        updateFetched(true);
        updateAddress(addr);
        updateTotalPrice(sumPrice.toPrecision(3));
      }
    
      const params = useParams();
      const tokenId = params.tokenId;
      if (!dataFetched)
        getNFTData(tokenId);
      return (
        <div className="profileClass" style={{ "min-height": "100vh" }}>
          <Navbar></Navbar>
          <div className="profileClass">
            <div className="flex text-center flex-col mt-11 md:text-2xl text-white">
              <div className="mb-5">
                <h2 className="font-bold">Wallet Address</h2>
                {address}
              </div>
            </div>
            <div className="flex flex-row text-center justify-center mt-10 md:text-2xl text-white">
              <div>
                <h2 className="font-bold">No. of NFTs</h2>
                {data.length}
              </div>
              <div className="ml-20">
                <h2 className="font-bold">Total Value</h2>
                {totalPrice} ETH
              </div>
            </div>
            <div className="flex flex-col text-center items-center mt-11 text-white">
              <h2 className="font-bold">Your NFTs</h2>
              <div className="flex justify-center flex-wrap max-w-screen-xl">
                {data.map((value, index) => {
                  return <NFTTile data={value} key={index}></NFTTile>;
                })}
              </div>
              <div className="mt-10 text-xl">
                {data.length == 0 ? "Oops, No NFT data to display (Are you logged in?)" : ""}
              </div>
            </div>
          </div>
        </div>
      )
    };
    

#### src/components/NFTPage.js

    import Navbar from "./Navbar";
    import axie from "../tile.jpeg";
    import { useLocation, useParams } from 'react-router-dom';
    import MarketplaceJSON from "../Marketplace.json";
    import axios from "axios";
    import { useState } from "react";
    
    export default function NFTPage(props) {
    
      const [data, updateData] = useState({});
      const [message, updateMessage] = useState("");
      const [currAddress, updateCurrAddress] = useState("0x");
      const [dataFetched, updateDataFetched] = useState(false);
      async function getNFTData(tokenId) {
        const ethers = require("ethers");
        //After adding your Hardhat network to your metamask, this code will get providers and signers
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const signer = provider.getSigner();
        //Pull the deployed contract instance
        let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
        //create an NFT Token
        const tokenURI = await contract.tokenURI(tokenId);
        const listedToken = await contract.getListedTokenForId(tokenId);
        let meta = await axios.get(tokenURI);
        meta = meta.data;
        console.log(listedToken);
    
        let item = {
          price: meta.price,
          tokenId: tokenId,
          seller: listedToken.seller,
          owner: listedToken.owner,
          image: meta.image,
          name: meta.name,
          description: meta.description,
        }
        console.log(item);
        updateData(item);
        updateDataFetched(true);
      }
    
      async function buyNFT(tokenId) {
        try {
          const ethers = require("ethers");
          //After adding your Hardhat network to your metamask, this code will get providers and signers
          const provider = new ethers.providers.Web3Provider(window.ethereum);
          const signer = provider.getSigner();
          //Pull the deployed contract instance
          let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer);
          const salePrice = ethers.utils.parseUnits(data.price, 'ether')
          let transaction = await contract.executeSale(tokenId, { value: salePrice });
          await transaction.wait();
    
          alert('You successfully bought the NFT!');
        }
        catch (e) {
          alert("Upload Error" + e)
        }
      }
    
      return (
        <div style={{ "min-height": "100vh" }}>
          <Navbar></Navbar>
          <div className="flex ml-20 mt-20">
            <img src={data.image} alt="" className="w-2/5" />
            <div className="text-xl ml-20 space-y-8 text-white shadow-2xl rounded-lg border-2 p-5">
              <div>
                Name: {data.name}
              </div>
              <div>
                Description: {data.description}
              </div>
              <div>
                Price: <span className="">{data.price + " ETH"}</span>
              </div>
              <div>
                Owner: <span className="text-sm">{data.owner}</span>
              </div>
              <div>
                Seller: <span className="text-sm">{data.seller}</span>
              </div>
              <div>
                {currAddress == data.owner || currAddress == data.seller ?
                  <div className="text-emerald-700">You are the owner of this NFT</div>
                  : <button className="enableEthereumButton bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm">Buy this NFT</button>
                }
    
                <div className="text-green text-center mt-3">{message}</div>
              </div>
            </div>
          </div>
        </div>
      )
    }
    

7.测试代码
------

我们在shell输入npm start 则会出现这样的页面

![](https://storage.googleapis.com/papyrus_images/511bb5e996725cb31ae5ed1df61d12b7e34cfb985ac6570e0f59eafa588f7d81.png)

![](https://storage.googleapis.com/papyrus_images/5b0227b3d7fb414d6cb8a7c43c9251d43d2cd055ab06e6899cc186eebb83fdfc.png)

---

*Originally published on [KnowYourself](https://paragraph.com/@knowyourself-2/alchemy-the-road-to-web3-nft)*
