大家好,我是帝哥(推特:@CoinmanLabs),今天帝哥带大家一起来看看第七周的任务。
2022 年 1 月,以太坊最大的 NFT 市场 Opensea 售出了约 250 万个 NFT,交易量为 50 亿美元**。**
2022 年 5 月,Solana 最大的 NFT 市场Magic Eden的交易量约为 1130 万美元,交易量为 2 亿美元。
这种规模只有通过出色的智能合约和可扩展的基础设施才能实现。因此我们第七周就来学下怎么构建一个NFT市场
在MetaMask中添加下面的链信息:
Network Name: Goerli Test Network
RPC base URL: https://eth-goerli.alchemyapi.io/v2/{INSERT YOUR API KEY}
Chain ID: 5
Block Explorer URL: https://goerli.etherscan.io/
本次课程我们继续使用repli来做
首先我们将本次需要的前端代码clone下来,具体操作如下:
输入我们第七周的项目课程的地址:
等到项目倒入完成,当项目完成后,我们需要去修改配置文件
然后在项目下执行
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>"
当我们上面做好了,我们在shell中输入一下命令,让系统帮助我们安装依赖等信息:
npm install dotenv --save
如果没有 Piñata 帐户,注册一个即可。当注册登录进去后我们需要去获取api_key.
我们需要新建一个key,同时将Admin权限开启,给自己的key命名,帝哥在这里用的是coinman。
当我们新建成功后,页面会提示一个有关key的信息,将它复制到安全的地方
同时将我们的.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>"
现在去修改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 ListedToken[](nftCount);
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 ListedToken[](itemCount);
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
}
找到部署合约的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);
});
上面帝哥给大家说了下这里我没通过.env来进行配置的,因为很多同学不会,所以这里帝哥就直接使用硬编码的方式来写了。
使用下面的命令部署合约
npx hardhat run --network goerli scripts/deploy.js
当你看到生成了一下的文件则说明你已经成功部署了
我们在部署的json文件中也可以找到合约的地址,我们去浏览器查看下帝哥部署的合约地址是:0x10Ba98F56e39be0437beA37fE2d903b8F6c9Ffe5
我们首先新建一个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,
}
});
};
为了使平台无缝工作,将前端与智能合约中的功能集成,需要将下面的代码直接覆盖即可,帝哥这里已经对代码进行了修改直接使用即可。
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>
);
}
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>
)
};
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>
)
}
我们在shell输入npm start 则会出现这样的页面
最后我们别忘了去填写表格哦,记得最后把你的repli的仓库公开哦,以上的就是第七周的。最后奖励大家看完教大家最简单完成第七周的任务的方式,直接把作者的github仓库clone下直接运行即可。
我是懂币帝,用技术带你领略区块链魅力,第一时间获取行业最新资讯:
推特:@CoinmanLabs
微信:CoinmanLabs(社群客服微信进群)