目錄
目錄- 目錄
- 1、ERC721的基礎知識
- 1.1、什么是不可替代代幣?
- 1.2、什么是 ERC-721?
- 1.3、什么是元資料
- 1.4、如何在鏈上保存NFT的影像
- 2、HardHat
- 3、創建專案
- 3.1、創建 NFT 市場
- 3.2、創建 NFT 智能合約
- 3.3、撰寫測驗腳本
- 4、將 NFT 部署到 Rinkeby 網路,在 OpenSea 上查看
- 4.1、部署 NFT市場
- 4.2、部署 NFT 721示例
- 4.3、對 NFT 721示例 合約在 Rinkeby 網路進行驗證
- 4.4、在 Rinkeby 網路鑄造 NFT
- 4.5、在 opensea 查看剛剛鑄造的NFT
- 5、專案原始碼
- 6、推薦閱讀
1、ERC721的基礎知識
1.1、什么是不可替代代幣?
NFT 是獨一無二的,每個令牌都有獨特的特征和價值,可以成為 NFT 的東西型別有收藏卡、藝術品、機票等,它們之間都有明顯的區別,不可互換,將不可替代代幣 (NFT) 視為稀有收藏品;而且大多數時候,還有它的元資料屬性,
1.2、什么是 ERC-721?
ERC-721(Ethereum Request for Comments 721)由 William Entriken、Dieter Shirley、Jacob Evans 和 Nastassia Sachs 于 2018 年 1 月提出,是一種不可替代的代幣標準,描述了如何在 EVM(以太坊虛擬機)兼容的區塊鏈上構建不可替代的代幣;它是不可替代代幣的標準介面;它有一套規則,可以很容易地使用 NFT,NFT 不僅是 ERC-721 型別;它們也可以是ERC-1155 令牌,
ERC-721 引入了 NFT 標準,換句話說,這種型別的 Token 是獨一無二的,并且可能具有與來自同一智能合約的另一個 Token 不同的價值,可能是由于它的年齡、稀有性甚至是其他類似自定義屬性等等,
所有 NFT 都有一個 uint256 型別的變數 tokenId,因此對于任何 ERC-721 合約,該對 contract address、uint256 tokenId 必須是全域唯一的,也就是說,一個 dApp 可以有一個“轉換器”,它使用 tokenId 作為輸入并輸出一些很酷的東西的影像,比如僵尸、武器、技能或貓、狗一類的!
1.3、什么是元資料
所有 NFT 都有元資料,您可以在最初的ERC/EIP 721 提案中了解這一點 , 基本上,社區發現在以太坊上存盤影像真的很費力而且很昂貴,如果你想存盤一張 8 x 8 的圖片,存盤這么多資料是相當便宜的,但如果你想要一張解析度不錯的圖片,你就需要花更多的 GAS 費用,
雖然 以太坊 2.0 將解決很多這些擴展難題,但目前,社區需要一個標準來幫助解決這個問題,這也就是元資料的存在原因,
{
"name": "mshk-logo-black",
"description": "mshk.top logo black",
"image": "https://bafybeihodzhbtntgml7t72maxill576ssax6md5kfu72aq4gd4p53oipn4.ipfs.infura-ipfs.io/",
"attributes": [
{
"trait_type": "customAttr",
"value": 100
}
]
}
name,NFT的代幣名稱
description,NFT的代幣描述
image,NFT影像的URL
attributes,NFT代幣的屬性,可以定義多個
一旦我們將 tokenId 分配給他們的 tokenURI,NFT 市場將能夠展示你的代幣,您可以在 Rinkeby 測驗網上的 OpenSea 市場上看到我使用元資料更新后的效果,類似展示 NFT 的市場 還有如 Mintable、Rarible,

1.4、如何在鏈上保存NFT的影像
您會在上面的元資料代碼示例中注意到,影像使用指向 IPFS 的 URL,這是一種流行的影像存盤方式,
IPFS 代表星際檔案系統,是一種點對點超媒體協議,旨在使網路更快、更安全、更開放,它允許任何人上傳檔案,并且該檔案被散列,因此如果它發生變化,它的散列也會發生變化,這是存盤影像的理想選擇,因為這意味著每次更新影像時,鏈上的 hash/tokenURI 也必須更改,這意味著我們可以記錄元資料的歷史記錄,將影像添加到 IPFS 上也非常容易,并且不需要運行服務器!
推薦使用 CoinTool 中的 IPFS 工具,
2、HardHat
關于 HardHat 的介紹以及安裝,可以參考文章 如何使用ERC20代幣實作買、賣功能并完成Dapp部署
3、創建專案
3.1、創建 NFT 市場
進入 hardhat 專案目錄,創建 contracts/ERC721/NftMarketplace.sol 檔案,內容如下:
$ cat contracts/ERC721/NftMarketplace.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
// Check out https://github.com/Fantom-foundation/Artion-Contracts/blob/5c90d2bc0401af6fb5abf35b860b762b31dfee02/contracts/FantomMarketplace.sol
// For a full decentralized nft marketplace
// 從Solidity v0.8.4開始,有一種方便且省 gas 的方式可以通過使用自定義錯誤向用戶解釋操作失敗的原因,
// 錯誤的語法類似于 事件的語法,它們必須與revert 陳述句一起使用,這會導致當前呼叫中的所有更改都被還原并將錯誤資料傳遞回呼叫者
error PriceNotMet(address nftAddress, uint256 tokenId, uint256 price);
error ItemNotForSale(address nftAddress, uint256 tokenId);
error NotListed(address nftAddress, uint256 tokenId);
error AlreadyListed(address nftAddress, uint256 tokenId);
error NoProceeds();
error NotOwner();
error NotApprovedForMarketplace();
error PriceMustBeAboveZero();
contract NftMarketplace is ReentrancyGuard {
// 保存賣家地址和價格
struct Listing {
uint256 price;
address seller;
}
// 加入市場串列事件
event ItemListed(
address indexed seller,
address indexed nftAddress,
uint256 indexed tokenId,
uint256 price
);
// 更新事件
event UpdateListed(
address indexed seller,
address indexed nftAddress,
uint256 indexed tokenId,
uint256 price
);
// 取消市場串列事件
event ItemCanceled(
address indexed seller,
address indexed nftAddress,
uint256 indexed tokenId
);
// 買入事件
event ItemBuy(
address indexed buyer,
address indexed nftAddress,
uint256 indexed tokenId,
uint256 price
);
// 保存NFT串列和賣家的對應狀態
mapping(address => mapping(uint256 => Listing)) private s_listings;
// 賣家地址和賣出的總金額
mapping(address => uint256) private s_proceeds;
modifier notListed(
address nftAddress,
uint256 tokenId,
address owner
) {
Listing memory listing = s_listings[nftAddress][tokenId];
if (listing.price > 0) {
revert AlreadyListed(nftAddress, tokenId);
}
_;
}
// 檢查賣家是否在串列中
modifier isListed(address nftAddress, uint256 tokenId) {
Listing memory listing = s_listings[nftAddress][tokenId];
if (listing.price <= 0) {
revert NotListed(nftAddress, tokenId);
}
_;
}
// 檢查 NFT 地址的 tokenId owner 是否為 spender
modifier isOwner(
address nftAddress,
uint256 tokenId,
address spender
) {
IERC721 nft = IERC721(nftAddress);
// 查找NFT的所有者,分配給零地址的 NFT 被認為是無效的,回傳NFT持有者地址
address owner = nft.ownerOf(tokenId);
if (spender != owner) {
revert NotOwner();
}
_;
}
/*
* @notice 將 NFT 加入到市場串列中,external 表示這是一個外部函式
* @param nftAddress Address of NFT contract
* @param tokenId Token ID of NFT
* @param price sale price for each item
*/
function listItem(
address nftAddress,
uint256 tokenId,
uint256 price
)
external
notListed(nftAddress, tokenId, msg.sender)
isOwner(nftAddress, tokenId, msg.sender)
{
if (price <= 0) {
// 終止運行并撤銷狀態更改
revert PriceMustBeAboveZero();
}
IERC721 nft = IERC721(nftAddress);
// 獲取單個NFT的批準地址,如果tokenId不是有效地址,拋出例外,
if (nft.getApproved(tokenId) != address(this)) {
revert NotApprovedForMarketplace();
}
// 存盤智能合約狀態
s_listings[nftAddress][tokenId] = Listing(price, msg.sender);
// 注冊事件
emit ItemListed(msg.sender, nftAddress, tokenId, price);
}
/*
* @notice 從NFT串列中洗掉 賣家資訊
* @param nftAddress Address of NFT contract
* @param tokenId Token ID of NFT
*/
function cancelListing(address nftAddress, uint256 tokenId)
external
isOwner(nftAddress, tokenId, msg.sender)
isListed(nftAddress, tokenId)
{
delete (s_listings[nftAddress][tokenId]);
// 注冊 事件
emit ItemCanceled(msg.sender, nftAddress, tokenId);
}
/*
* @notice 允許買家使用ETH,從賣家串列中買入 NFT
* nonReentrant 方法 防止合約被重復呼叫
* @param nftAddress NFT 合約地址
* @param tokenId NFT 的通證 ID
*/
function buyItem(address nftAddress, uint256 tokenId)
external
payable
isListed(nftAddress, tokenId)
nonReentrant
{
// 獲取賣家串列,并判斷支付的ETH是否小于賣家的價格
Listing memory listedItem = s_listings[nftAddress][tokenId];
if (msg.value < listedItem.price) {
revert PriceNotMet(nftAddress, tokenId, listedItem.price);
}
// 更新賣家賣出的金額
s_proceeds[listedItem.seller] += msg.value;
// Could just send the money...
// https://fravoll.github.io/solidity-patterns/pull_over_push.html
// 從賣家串列中洗掉
delete (s_listings[nftAddress][tokenId]);
// 將 NFT(tokenId) 所有權從 listedItem.seller 轉移到 msg.sender
IERC721(nftAddress).safeTransferFrom(
listedItem.seller,
msg.sender,
tokenId
);
//注冊買家事件
emit ItemBuy(msg.sender, nftAddress, tokenId, listedItem.price);
}
/*
* @notice 賣家更新NFT在市場上的價格
* @param nftAddress Address of NFT contract
* @param tokenId Token ID of NFT
* @param newPrice Price in Wei of the item
*/
function updateListing(
address nftAddress,
uint256 tokenId,
uint256 newPrice
)
external
isListed(nftAddress, tokenId)
nonReentrant
isOwner(nftAddress, tokenId, msg.sender)
{
s_listings[nftAddress][tokenId].price = newPrice;
emit UpdateListed(msg.sender, nftAddress, tokenId, newPrice);
}
/*
* @notice 將ETH轉移到其他帳號,同時設定收益余額為0
*/
function withdrawProceeds() external {
uint256 proceeds = s_proceeds[msg.sender];
if (proceeds <= 0) {
revert NoProceeds();
}
s_proceeds[msg.sender] = 0;
// 將 ETH 發送到地址的方法,關于此語法更多介紹可以參考下面鏈接
// https://ethereum.stackexchange.com/questions/96685/how-to-use-address-call-in-solidity
(bool success, ) = payable(msg.sender).call{value: proceeds}("");
require(success, "Transfer failed");
}
/*
* @notice 獲取NFT賣家串列
*/
function getListing(address nftAddress, uint256 tokenId)
external
view
returns (Listing memory)
{
return s_listings[nftAddress][tokenId];
}
// 獲取 seller 賣出的總金額
function getProceeds(address seller) external view returns (uint256) {
return s_proceeds[seller];
}
}
從 Solidity v0.8.4開始,有一種方便且省 GAS 的方式可以通過使用自定義錯誤向用戶解釋操作失敗的原因,錯誤的語法類似于事件的語法,它們必須與 revert 陳述句一起使用,這會導致當前呼叫中的所有更改都被還原并將錯誤資料傳遞回呼叫者,
??自定義錯誤是在智能合約主體之外宣告的,當錯誤被拋出時,在 Solidity 中意味著當某些檢查和條件失敗,周圍函式的執行被“還原”,
代碼中主要內容介紹:
- notListed、isListed、isOwner是函式修飾符的應用,
- listItem方法,將
NFT加入到串列,會做一些權限驗證,其中用到了函式修飾符和事件 - cancelListing方法,從串列中洗掉
NFT,將NFT下架, - buyItem方法,購買
NFT,專案中主要用ETH來交換NFT資產,也可以用其他數字資產進行交換,同時會更新賣家余額,從listItem中下架NFT, - updateListing方法,更新
NFT的價格, - withdrawProceeds方法,將賣出的收益從合約中轉移給賣家,
- getListing方法,根據
NFT地址和tokenId,回傳賣家和價格資訊, - getProceeds方法,查看賣家賣出后的收益,
3.2、創建 NFT 智能合約
在撰寫測驗腳本前,我們需要一個 NFT的智能合約示例,以便我們鑄造的 NFT可以在市場上展示、銷售,我們將遵守 ERC721 令牌規范,我們將從 OpenZeppelin 的 ERC721URIStorage 庫繼承,
進入 hardhat 專案目錄,創建 contracts/ERC721/MSHK721NFT.sol 檔案,內容如下:
$ cat contracts/ERC721/MSHK721NFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "hardhat/console.sol";
contract MSHK721NFT is ERC721URIStorage, Ownable {
// 遞增遞減計數器
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
// 宣告事件
event NFTMinted(uint256 indexed tokenId);
constructor() ERC721("MSHKNFT", "MyNFT") {}
/**
* 制作NFT,回傳鑄造的 NFT ID
* @param recipient 接收新鑄造NFT的地址.
* @param tokenURI 描述 NFT 元資料的 JSON 檔案
*/
function mintNFT(address recipient, string memory tokenURI)
external
onlyOwner
returns (uint256)
{
// 遞增
_tokenIds.increment();
// 獲取當前新的 TokenId
uint256 newTokenId = _tokenIds.current();
// 鑄造NFT
_safeMint(recipient, newTokenId);
// 保存NFT URL
_setTokenURI(newTokenId, tokenURI);
// 注冊事件
emit NFTMinted(newTokenId);
return newTokenId;
}
function getTokenCounter() public view returns (uint256) {
return _tokenIds.current();
}
}
上面的代碼中,通過 mintNFT 方法鑄造 NFT,主要有2個引數,第1個引數是接收NFT 的地址,第2個引數是 NFT 的 URL 地址,也就是上文中提到的元資料地址,
3.3、撰寫測驗腳本
在撰寫測驗腳本前,我們先通過 IPFS工具,上傳我們的圖片和元資料檔案,下面是我們已經上傳好的2個元資料檔案:
檔案1,內容如下:
{
"name": "mshk-logo-black",
"description": "mshk.top logo black",
"image": "https://bafybeihodzhbtntgml7t72maxill576ssax6md5kfu72aq4gd4p53oipn4.ipfs.infura-ipfs.io/",
"attributes": [
{
"trait_type": "customAttr",
"value": 100
}
]
}
檔案2,內容如下:
{
"name": "mshk-logo-blue",
"description": "mshk.top logo blue",
"image": "https://bafybeifxkvzedhwclmibidf5hjoodwqkk2vlbbrlhd3bxbl3wzmkmyrvpq.ipfs.infura-ipfs.io/",
"attributes": [
{
"trait_type": "customAttr",
"value": 200
}
]
}
進入 hardhat 專案目錄,創建 test/ERC721/01_NFT.js 測驗檔案,內容如下:
const { expect } = require("chai");
const { ethers } = require("hardhat");
/**
* 運行測驗方法:
* npx hardhat test test/ERC721/01_NFT.js
*/
describe("NFT MarketPlace Test", () => {
// NFT 元資料1
const TOKEN_URI1 = "https://bafybeif5jtlbetjp2nzj64gstexywpp53efr7yynxf4qxtmf5lz6seezia.ipfs.infura-ipfs.io";
// NFT 元資料2
const TOKEN_URI2 = "https://bafybeibyb2rdn6raav4ozyxub2r5w4vh3wmw46s6bi54eq7syjzfkmbjn4.ipfs.infura-ipfs.io";
let owner;
let addr1;
let addr2;
let addrs;
let nftMarketplaceContractFactory;
let nftContractFactory;
let nftMarketplaceContract;
let nftContract;
let IDENTITIES;
beforeEach(async () => {
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();
IDENTITIES = {
[owner.address]: "OWNER",
[addr1.address]: "DEPLOYER",
[addr2.address]: "BUYER_1",
}
var NFTMarketplaceContractName = "NftMarketplace";
var NFTContractName = "MSHK721NFT"
// 獲取 NFTMarketplace 實體
nftMarketplaceContractFactory = await ethers.getContractFactory(NFTMarketplaceContractName);
// 部署 NFTMarketplace 合約
nftMarketplaceContract = await nftMarketplaceContractFactory.deploy()
// 獲取 nftContract 實體
nftContractFactory = await ethers.getContractFactory(NFTContractName);
// 部署 nftContract 合約
nftContract = await nftContractFactory.deploy()
console.log(`owner:${owner.address}`)
console.log(`addr1:${addr1.address}`)
console.log(`addr2:${addr2.address}`)
//
console.log(`${NFTMarketplaceContractName} Token Contract deployed address -> ${nftMarketplaceContract.address}`);
//
console.log(`${NFTContractName} Token Contract deployed address -> ${nftContract.address} owner:${await nftContract.owner()}`);
});
it("mint and list and buy item", async () => {
console.log(`Minting NFT for ${addr1.address}`)
// 為 addr1 鑄造一個 NFT
let mintTx = await nftContract.connect(owner).mintNFT(addr1.address, TOKEN_URI1)
let mintTxReceipt = await mintTx.wait(1)
// 非常量(既不pure也不view)函式的回傳值僅在函式被鏈上呼叫時才可用(即,從這個合約或從另一個合約)
// 當從鏈下(例如,從 ethers.js 腳本)呼叫此類函式時,需要在交易中執行它,并且回傳值是該交易的哈希值,因為不知道交易何時會被挖掘并添加到區塊鏈中
// 為了在從鏈下呼叫非常量函式時獲得它的回傳值,可以發出一個包含將要回傳的值的事件
let tokenId = mintTxReceipt.events[0].args.tokenId
expect(tokenId).to.equal(1);
// 授權 市場合約 可以操作這個NFT
console.log("Approving Marketplace as operator of NFT...")
let approvalTx = await nftContract
.connect(addr1)
.approve(nftMarketplaceContract.address, tokenId)
await approvalTx.wait(1)
// NFT交易價格 10 ETH
let PRICE = ethers.utils.parseEther("10")
// 將 NFT 加入到串列
console.log("Listing NFT...")
let listItemTX = await nftMarketplaceContract
.connect(addr1)
.listItem(nftContract.address, tokenId, PRICE)
await listItemTX.wait(1)
console.log("NFT Listed with token ID: ", tokenId.toString())
const mintedBy = await nftContract.ownerOf(tokenId)
// 檢查 nft 的 owner 是否為 addr1
expect(mintedBy).to.equal(addr1.address)
console.log(`NFT with ID ${tokenId} minted and listed by owner ${mintedBy} with identity ${IDENTITIES[mintedBy]}. `)
//---- Buy
// 根據 tokenId 獲取 NFT
let listing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
let price = listing.price.toString()
// 使用 addr2 從 nftMarketplaceContract 買入 TOKEN_ID 為 0 的NFT
const buyItemTX = await nftMarketplaceContract
.connect(addr2)
.buyItem(nftContract.address, tokenId, {
value: price,
})
await buyItemTX.wait(1)
console.log("NFT Bought!")
const newOwner = await nftContract.ownerOf(tokenId)
console.log(`New owner of Token ID ${tokenId} is ${newOwner} with identity of ${IDENTITIES[newOwner]} `)
//---- proceeds
const proceeds = await nftMarketplaceContract.getProceeds(addr1.address)
const proceedsValue = https://www.cnblogs.com/lion.net/p/ethers.utils.formatEther(proceeds.toString())
console.log(`Seller ${owner.address} has ${proceedsValue} eth!`)
//---- withdrawProceeds
const addr1OldBalance = await ethers.provider.getBalance(addr1.address);
await nftMarketplaceContract.connect(addr1).withdrawProceeds()
const addr1NewBalance = await ethers.provider.getBalance(addr1.address);
console.log(`${addr1.address} old:${ethers.utils.formatEther(addr1OldBalance)} eth,withdrawProceeds After:${ethers.utils.formatEther(addr1NewBalance)} eth!`)
});
it("update and cancel nft item", async () => {
// 為 addr2 鑄造一個 NFT
let mintTx = await nftContract.connect(owner).mintNFT(addr2.address, TOKEN_URI2)
let mintTxReceipt = await mintTx.wait(1)
// 非常量(既不pure也不view)函式的回傳值僅在函式被鏈上呼叫時才可用(即,從這個合約或從另一個合約)
// 當從鏈下(例如,從 ethers.js 腳本)呼叫此類函式時,需要在交易中執行它,并且回傳值是該交易的哈希值,因為不知道交易何時會被挖掘并添加到區塊鏈中
// 為了在從鏈下呼叫非常量函式時獲得它的回傳值,可以發出一個包含將要回傳的值的事件
let tokenId = mintTxReceipt.events[0].args.tokenId
// 授權 市場合約 可以操作這個NFT
console.log("Approving Marketplace as operator of NFT...")
approvalTx = await nftContract.connect(addr2).approve(nftMarketplaceContract.address, tokenId)
await approvalTx.wait(1)
// NFT交易價格 0.1 ETH
PRICE = ethers.utils.parseEther("0.1")
// 將 NFT 加入到串列
console.log("Listing NFT...")
listItemTX = await nftMarketplaceContract.connect(addr2).listItem(nftContract.address, tokenId, PRICE)
await listItemTX.wait(1)
console.log("NFT Listed with token ID: ", tokenId.toString())
console.log(`Updating listing for token ID ${tokenId} with a new price`)
listing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
let oldPrice = listing.price.toString()
console.log(`oldPrice: ${ethers.utils.formatEther(oldPrice.toString())}`)
// 更新價格
const updateTx = await nftMarketplaceContract.connect(addr2).updateListing(nftContract.address, tokenId, ethers.utils.parseEther("0.5"))
// 等待鏈上處理
const updateTxReceipt = await updateTx.wait(1)
// 從事件中獲取更新的價格
const updatedPrice = updateTxReceipt.events[0].args.price
console.log(`updated price: ${ethers.utils.formatEther(updatedPrice.toString())}`)
// 獲取資訊,確認價格是否有變更.
const updatedListing = await nftMarketplaceContract.getListing(
nftContract.address,
tokenId
)
console.log(`Updated listing has price of ${ethers.utils.formatEther(updatedListing.price.toString())}`)
//----------cancel
let tx = await nftMarketplaceContract.connect(addr2).cancelListing(nftContract.address, tokenId)
await tx.wait(1)
console.log(`NFT with ID ${tokenId} Canceled...`)
// Check cancellation.
const canceledListing = await nftMarketplaceContract.getListing(nftContract.address, tokenId)
console.log("Seller is Zero Address (i.e no one!)", canceledListing.seller)
});
});
上面的測驗腳本中,我們分成兩部分,注釋比較詳細,下面是簡要介紹這兩部分測驗的功能,
??第1部分:
- 為
addr1用戶鑄1個NFT - 授權
NFT市場可以操作這個addr1的 NFT, - 將
NFT加入到NFT市場,設定價格為10ETH, - 使用
addr2用戶購買addr1的NFT, - 查看
addr1在NFT市場的余額 - 將
NFT市場中的余額取出到addr1的余額,對比前后余額資料,
第2部分:
- 為
addr2用戶鑄1個NFT - 授權
NFT市場可以操作這個addr2的 NFT, - 將
NFT加入到NFT市場,設定價格為0.1ETH, - 將
addr2的NFT價格從0.1ETH 更新為0.5ETH,進行資料對比輸出, - 從
NFT市場中下架addr2的 NFT,
下面是我們運行測驗腳本的效果:

到目前為止,我們已經完成了 NFT 的創建,并將 NFT 加入到市場完成了買、賣、查看銷售后的余額,轉帳給賣家等功能,
專案的原始碼都保存在 Github:https://github.com/idoall/NFT-ERC721-NFTMarketPlace
克隆專案到本地后,進入 hardhat 專案目錄,先執行 yarn install 下載依賴包,
$ yarn install
yarn install v1.22.19
warning package.json: No license field
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning hardhat-project: No license field
[1/4] ?? Resolving packages...
[2/4] ?? Fetching packages...
[3/4] ?? Linking dependencies...
warning " > @nomiclabs/[email protected]" has incorrect peer dependency "@nomiclabs/hardhat-ethers@^2.0.0".
warning " > @openzeppelin/[email protected]" has incorrect peer dependency "@nomiclabs/hardhat-ethers@^2.0.0".
warning "hardhat-deploy > [email protected]" has incorrect peer dependency "ethers@~5.5.0".
[4/4] ?? Building fresh packages...
? Done in 15.42s.
安裝完依賴包后,運行npx hardhat test test/ERC721/01_NFT.js 命令,可以看到和上圖一樣的效果,
$ npx hardhat test test/ERC721/01_NFT.js
Compiled 16 Solidity files successfully
NFT MarketPlace Test
owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
addr1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8
addr2:0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
NftMarketplace Token Contract deployed address -> 0x5FbDB2315678afecb367f032d93F642f64180aa3
MSHK721NFT Token Contract deployed address -> 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Minting NFT for 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Approving Marketplace as operator of NFT...
Listing NFT...
NFT Listed with token ID: 1
NFT with ID 1 minted and listed by owner 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 with identity DEPLOYER.
NFT Bought!
New owner of Token ID 1 is 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC with identity of BUYER_1
Seller 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 10.0 eth!
0x70997970C51812dc3A010C7d01b50e0d17dc79C8 old:9999.999797616067546951 eth,withdrawProceeds After:10009.9997570794102017 eth!
? mint and list and buy item (232ms)
owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
addr1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8
addr2:0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
NftMarketplace Token Contract deployed address -> 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
MSHK721NFT Token Contract deployed address -> 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 owner:0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Approving Marketplace as operator of NFT...
Listing NFT...
NFT Listed with token ID: 1
Updating listing for token ID 1 with a new price
oldPrice: 0.1
updated price: 0.5
Updated listing has price of 0.5
NFT with ID 1 Canceled...
Seller is Zero Address (i.e no one!) 0x0000000000000000000000000000000000000000
? update and cancel nft item (156ms)
2 passing (2s)
4、將 NFT 部署到 Rinkeby 網路,在 OpenSea 上查看
打開 hardhat.config.js 檔案,編輯內容如下并保存:
- 修改里面的
RINKEBY_RPC_URL為你的地址,如果沒有帳號,可以去 alchemy.com 注冊一個,以后開發區塊鏈時會經常使用到, - 修改
PRIVATE_KEY為你要部署的帳號私鑰,
4.1、部署 NFT市場
運行下面的命令,將 NFT市場 部署到 Rinkeby 網路:
$ npx hardhat run script/ERC721/01-deploy-NftMarketplace.js --network rinkeby
----------------------------------------------------
deployer address -> 0xbB0a92d634D7b9Ac69079ed0e521CC2e0a97c420
NftMarketplace Contract deployed address -> 0x48aD115EE899Cc01d6Fd2Ea9BC3fE5bd7d3E1B1C
在 Rinkeby 網路,查看我們創建的NFT交易市場合約,效果如下圖:

4.2、部署 NFT 721示例
運行下面的命令,將 NFT示例 部署到 Rinkeby 網路:
$ npx hardhat run script/ERC721/02-deploy-MSHKNFT.js --network rinkeby
----------------------------------------------------
deployer address -> 0xbB0a92d634D7b9Ac69079ed0e521CC2e0a97c420
MSHK721NFT Contract deployed address -> 0x4b241b36D445E46dAE1916f5A0e76dfE470df115
----------------------------------------------------
記住我們創建的合約地址
0x4b241b36D445E46dAE1916f5A0e76dfE470df115,后面我們會對合約進行線上驗證,
在 Rinkeby 網路,查看我們創建的NFT721合約,效果如下圖:

4.3、對 NFT 721示例 合約在 Rinkeby 網路進行驗證
驗證 NFT示例 合約:
$ npx hardhat verify --contract contracts/ERC721/MSHK721NFT.sol:MSHK721NFT 0x4b241b36D445E46dAE1916f5A0e76dfE470df115 --network rinkeby
Nothing to compile
Successfully submitted source code for contract
contracts/ERC721/MSHK721NFT.sol:MSHK721NFT at 0x4b241b36D445E46dAE1916f5A0e76dfE470df115
for verification on the block explorer. Waiting for verification result...
Successfully verified contract MSHK721NFT on Etherscan.
https://rinkeby.etherscan.io/address/0x4b241b36D445E46dAE1916f5A0e76dfE470df115#code
4.4、在 Rinkeby 網路鑄造 NFT
我們打開 Rinkeby 網路,瀏覽剛剛創建的 NFT 721示例 合約,為地址 0x0BFd206c851729590DDAdfCa9439b30aD2AAbf9F 創建一個 NFT,NFT 的元資料,使用 IPFS工具創建好的元資料地址 https://bafybeif5jtlbetjp2nzj64gstexywpp53efr7yynxf4qxtmf5lz6seezia.ipfs.infura-ipfs.io,
操作步驟如下圖:

創建 NFT 后我們可以通過 交易哈希 看到,NFT合約 0x4b241b36d445e46dae1916f5a0e76dfe470df115,剛剛創建的 Token ID 為 1的 Token,

4.5、在 opensea 查看剛剛鑄造的NFT
瀏覽以下地址 https://testnets.opensea.io/assets/rinkeby/0x4b241b36d445e46dae1916f5a0e76dfe470df115/1 可以看到我們剛剛鑄的NFT 圖片,
在URL部分,rinkeby 表示網路名稱,0x4b241b36d445e46dae1916f5a0e76dfe470df115是 NFT721 的合約地址,1 是 Token ID,

至此,我們完成了如何鑄造NFT,以及完善一個可以買、賣交易的 NFT市場,包括發布到 rinkeby 網路后,在 opensea 測驗網路查看,
如果發布到主網,將
rinkeby更改為ethmainnet即可,
5、專案原始碼
Github:https://github.com/idoall/NFT-ERC721-NFTMarketPlace
6、推薦閱讀
常用詞匯表
??Solidity v0.8.4 Custom Error
轉載宣告:可以轉載, 但必須以超鏈接形式標明文章原始出處和作者資訊及著作權宣告,謝謝合作!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/501086.html
標籤:區塊鏈
上一篇:鮮衣怒馬散盡千金,Vue3.0+Tornado6前后端分離集成Web3.0之Metamask錢包區塊鏈虛擬貨幣三方支付功能
下一篇:智能合約安全性
