NFT的介绍
NFT全称为Non-Fungible Token,非同质化代币,是用于表示数字资产的唯一加密货币令牌,具有不可分割、不可代替、独一无二等特点。在区块链上,数字加密货币分为原生币和代币两大类。原生币如大家熟悉的比特币、以太币等,拥有自己的主链,使用链上的交易来维护账本数据。代币是依附于现有的区块链,使用智能合约来进行账本的记录,如依附于以太坊上而发布的token。代币之中又可分为同质化和非同质化两种。同质化代币即FT(Fungible Token),互相可以替代、可接近无限拆分的token。例如,你手里有一个比特币与我手里的一个比特币,本质上没有任何区别,这就是同质化,就是同质化币[1]。
①NFT非同质化代币的应用:NFT非同质化代币由于其非同质化、不可拆分的特性,使得它可以和现实世界中的一些商品绑定。换言之,其实就是发行在区块链上的数字资产,这个资产可以是游戏道具、数字艺术品、门票等,并且具有唯一性和不可复制性。由于NFT具备天然的收藏属性和便于交易,加密艺术家们可以利用NFT创造出独一无二的数字艺术品[2]。
②NFT非同质化代币的特点:不可分割:NFT非同质化代币是唯一的、且不可分割成更小的单位。任何一枚比特币、以太坊等都可以换成另外一枚 比特币或以太坊,且他们都可以分割成更小的单位,例如0.1btc或者0.01btc。独一无二:NFT非同质化代币是不可互换的资产,它具有独一无二的属性。这意味着,如果NFT资产本身是稀缺的,它也会承载其价值,不可篡改或者复制。透明度:买方可以看到每个过往卖家和投标者的历史,他们付出了什么价格,以及任何数字商品的当下市场状况。
在这之前,有使用Chainlink VRF使用可验证的 RNG 构建的NFT,他创建根据现实世界数据变化的动态 NFT。通过使用去中心化的预言机来获取数据,将随机NFT添加到OpenSea中[2]。
数字化交易市场搭建
入门
建立数字化交易市场需要做的第一件事是编写智能合约。
市场将由两个主要的智能合约组成:
①用于铸造 NFT 的 NFT 合约
②促进 NFT 销售的市场合约
为了编写 NFT,我们可以使用可通过OpenZeppelin获得的ERC721标准。
首先,转到https://remix.ethereum.org/并创建一个名为Marketplace.sol的新文件。
可以通过导入从 Open Zeppelin 开始所需的合约来开始:
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "hardhat/console.sol";
创建 NFT 合约
contract NFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address contractAddress;
constructor(address marketplaceAddress) ERC721("Eat The Blocks NFTs", "ETBNFT") {
contractAddress = marketplaceAddress;
}
function createToken(string memory tokenURI) public returns (uint) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
setApprovalForAll(contractAddress, true);
return newItemId;
}
}
构造函数接受marketplaceAddress地址的参数,保存值并使其在智能合约中可用。这样当有人想创建合约时,合约可以允许市场合约批准将代币从所有者转移到卖家。该newItemId值是从函数返回的,因为我们将在我们的客户端应用程序中需要它来了解tokenId智能合约生成的动态值。
创建市场合约
市场合约比NFT合约更为复杂,但也是该项目的重要部分。
contract NFTMarket is ReentrancyGuard {
using Counters for Counters.Counter;
Counters.Counter private _itemIds;
Counters.Counter private _itemsSold;
struct MarketItem {
uint itemId;
address nftContract;
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
}
mapping(uint256 => MarketItem) private idToMarketItem;
event MarketItemCreated (
uint indexed itemId,
address indexed nftContract,
uint256 indexed tokenId,
address seller,
address owner,
uint256 price
);
function getMarketItem(uint256 marketItemId) public view returns (MarketItem memory) {
return idToMarketItem[marketItemId];
}
function createMarketItem(
address nftContract,
uint256 tokenId,
uint256 price
) public payable nonReentrant {
require(price > 0, "Price must be at least 1 wei");
_itemIds.increment();
uint256 itemId = _itemIds.current();
idToMarketItem[itemId] = MarketItem(
itemId,
nftContract,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
emit MarketItemCreated(
itemId,
nftContract,
tokenId,
msg.sender,
address(0),
price
);
}
function createMarketSale(
address nftContract,
uint256 itemId
) public payable nonReentrant {
uint price = idToMarketItem[itemId].price;
uint tokenId = idToMarketItem[itemId].tokenId;
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
idToMarketItem[itemId].seller.transfer(msg.value);
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
idToMarketItem[itemId].owner = payable(msg.sender);
_itemsSold.increment();
}
function fetchMarketItem(uint itemId) public view returns (MarketItem memory) {
MarketItem memory item = idToMarketItem[itemId];
return item;
}
function fetchMarketItems() public view returns (MarketItem[] memory) {
uint itemCount = _itemIds.current();
uint unsoldItemCount = _itemIds.current() - _itemsSold.current();
uint currentIndex = 0;
MarketItem[] memory items = new MarketItem[](unsoldItemCount);
for (uint i = 0; i < itemCount; i++) {
if (idToMarketItem[i + 1].owner == address(0)) {
uint currentId = idToMarketItem[i + 1].itemId;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
function fetchMyNFTs() public view returns (MarketItem[] memory) {
uint totalItemCount = _itemIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender) {
uint currentId = idToMarketItem[i + 1].itemId;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
}
继承来自ReentrancyGuard的nonReentrant,它可以应用于函数以确保没有对它们的嵌套(可重入)调用。我们在MarketItem的数据据结构体中储存我们想要在市场上提供的项目的记录。idToMarketltem是一种可以允许我们在IDs和MarketIItem之间创建关键值得映射。createMarketItem得作用是把NFT转移到市场上的交易地址,这样NFT就可以出售了。createMarketSale是作为NFT和Eth之间的介质,使他们可以在交易中进行转换。fetchMarketItems可以帮助你查看所有在市场上出售的NFT。fetchMyNFTs则可以查看你的购买记录。
构建前端
在前端的构造中使用了已有的初始项目。
marketplace_starter
构建用户界面
构建用户界面是本数字化市场的重点项目,包括以下功能:获取NFT、创建NFT并将其出售、允许用户购买NFT以及允许用户查看他们自己的NFT。
loadNFTs函数通过调用fetchMarkItems来返回市场中未出售的NFT。
async function loadNFTs() {
const provider = new ethers.providers.JsonRpcProvider()
const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, provider)
const data = await marketContract.fetchMarketItems()
const items = await Promise.all(data.map(async i => {
const tokenUri = await tokenContract.tokenURI(i.tokenId)
const meta = await axios.get(tokenUri)
let price = web3.utils.fromWei(i.price.toString(), 'ether');
let item = {
price,
tokenId: i.tokenId.toNumber(),
seller: i.seller,
owner: i.owner,
image: meta.data.image,
}
return item
}))
setNfts(items)
setLoaded('loaded')
}
Creating an NFT and placing it for sale用于创造NFT并将其投放市场,包括createToken和createMarketItem来创建新得Token和放置出售的项目。
async function buyNft(nft) {
const web3Modal = new Web3Modal({
network: "mainnet",
cacheProvider: true,
});
const connection = await web3Modal.connect()
const provider = new ethers.providers.Web3Provider(connection)
const signer = provider.getSigner()
const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
const price = web3.utils.toWei(nft.price.toString(), 'ether');
const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, {
value: price
})
await transaction.wait()
loadNFTs()
}
关键的一步是允许用户从市场上购买NFT
async function buyNft(nft) {
const web3Modal = new Web3Modal({
network: "mainnet",
cacheProvider: true,
});
const connection = await web3Modal.connect()
const provider = new ethers.providers.Web3Provider(connection)
const signer = provider.getSigner()
const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
const price = web3.utils.toWei(nft.price.toString(), 'ether');
const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, {
value: price
})
await transaction.wait()
loadNFTs()
}
Allowing a user to view their own NFTs实现了让用户可以查看他们购买的NFT,从fetchMyNFTs智能合约中调用了该函数。
async function loadNFTs() {
const web3Modal = new Web3Modal({
network: "mainnet",
cacheProvider: true,
});
const connection = await web3Modal.connect()
const provider = new ethers.providers.Web3Provider(connection)
const signer = provider.getSigner()
const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
const data = await marketContract.fetchMyNFTs()
const items = await Promise.all(data.map(async i => {
const tokenUri = await tokenContract.tokenURI(i.tokenId)
const meta = await axios.get(tokenUri)
let price = web3.utils.fromWei(i.price.toString(), 'ether');
let item = {
price,
tokenId: i.tokenId.toNumber(),
seller: i.seller,
owner: i.owner,
image: meta.data.image,
}
return item
}))
setNfts(items)
setLoaded('loaded')
}
最后就是我们开发交易市场的盈利项目,从交易中收取上市费用,这仅需几行代码就可以完成。首先在Contracts中打开NFTMarkets.sol文件,需要设置使用上市的价格,还需要创建一个变量来存储合约的所有者。
可以在_itemsSold初始化以下代码:
address payable owner;
uint256 listingPrice = 0.01 ether;
然后创建一个constructor来存储地址:
constructor() {
owner = payable(msg.sender);
}
在createMarketItem功能在,确保上市的费用包含了手续费:
require(price > 0, "Price must be at least 1 wei"); // below this line