当前位置:网站首页>Web3.0:构建 NFT 市场(一)
Web3.0:构建 NFT 市场(一)
2022-08-01 00:08:00 【InfoQ】
基础知识
初始化和配置
npx create-next-app crayon-nft-marketplace
pages/index.js
npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers web3modal axios --save
npm install @openzeppelin/contracts --save
npm install [email protected] --save
npm install [email protected] [email protected] [email protected] -D
npx tailwindcss init -p
智能合约
npx hardhat
hardhat.config.js
Infura
chainId
1337
module.exports = {
solidity: "0.7.3",
networks: {
hardhat: {
chainId: 1337,
},
},
};
Polygon
Polygon Mumbai
URL
module.exports = {
solidity: "0.7.3",
networks: {
hardhat: {
chainId: 80001,
},
mumbai: {
url: "https://rpc-mumbai.maticvigil.com/",
},
mainnet: {
url: "https://polygon-rpc.com/",
},
},
};
.secret
hardhat.config.js
solidity
const fs = require("fs");
require("@nomiclabs/hardhat-waffle");
const privateKey = fs.readFileSync(".secret").toString().trim();
module.exports = {
solidity: "0.8.4",
networks: {
hardhat: {
chainId: 80001,
},
mumbai: {
url: "https://rpc-mumbai.maticvigil.com/",
accounts: [privateKey],
},
mainnet: {
url: "https://polygon-rpc.com/",
accounts: [privateKey],
},
ropsten: {
url: `https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161`,
accounts: [privateKey],
chainId: 3,
},
},
};
创建智能合约
contracts
CrayonNft.sol
CrayonNftMarket.sol
OpenZeppelin
CrayonNft.sol
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
ERC721.sol
:核心和元数据扩展,具有基本 URI 机制
ERC721URIStorage.sol
:一种更灵活但更昂贵的元数据存储方式。
Counters.sol
:一种获取只能递增、递减或重置的计数器的简单方法。对于 ID 生成、合同活动计数等非常有用。
CrayonNft.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract CrayonNft is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address contractAddress;
constructor(address marketplaceAddress) ERC721("Crayon Nft Tokens", "METT") {
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;
}
}
CrayonNftMarket.sol
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
ERC721.sol
:核心和元数据扩展,具有基本 URI 机制
Counters.sol
:一种获取只能递增、递减或重置的计数器的简单方法。对于 ID 生成、合同活动计数等非常有用。
ReentrancyGuard.sol
:在某些功能期间可以防止重入的修饰符。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "hardhat/console.sol";
contract CrayonNftMarket is ReentrancyGuard {
using Counters for Counters.Counter;
Counters.Counter private _itemIds;
Counters.Counter private _itemsSold;
address payable owner;
uint256 listingPrice = 0.025 ether;
constructor() {
owner = payable(msg.sender);
}
// 定义 NFT 销售属性
struct MarketItem {
uint itemId;
address nftContract;
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
mapping(uint256 => MarketItem) private idToMarketItem;
// 市场项目创建触发器
event MarketItemCreated (
uint indexed itemId,
address indexed nftContract,
uint256 indexed tokenId,
address seller,
address owner,
uint256 price,
bool sold
);
// 返回价格
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
// 在市场上销售一个NFT
function createMarketItem(
address nftContract,
uint256 tokenId,
uint256 price
) public payable nonReentrant {
require(price > 0, "Price must be at least 1 wei"); // 防止免费交易
require(msg.value == listingPrice, "Price must be equal to listing price"); // 交易价格必须和NFT单价相等
_itemIds.increment();
uint256 itemId = _itemIds.current();
idToMarketItem[itemId] = MarketItem(
itemId,
nftContract,
tokenId,
payable(msg.sender),
payable(address(0)),
price,
false
);
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); // 将NFT的所有权转让给合同
emit MarketItemCreated(
itemId,
nftContract,
tokenId,
msg.sender,
address(0),
price,
false
);
}
// 创建销售市场项目转让所有权和资金
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); // 将价值转移给卖方
idToMarketItem[itemId].owner = payable(msg.sender);
idToMarketItem[itemId].sold = true;
_itemsSold.increment();
console.log("Nft Contract Address:",nftContract);
console.log("tokenId:",tokenId);
payable(owner).transfer(listingPrice);
}
// 返回市场上所有未售出的商品
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 = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
// 获取用户购买的NFT
function fetchMyNFTs() public view returns (MarketItem[] memory) {
uint totalItemCount = _itemIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
for (uint j = 0; j < totalItemCount; j++) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint256 i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
// 获取卖家制作的NFT
function fetchItemsCreated() 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].seller == msg.sender) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].seller == msg.sender) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
}
CrayonNftMarket.sol
测试智能合约
test
test.js
NFT
Chai
Hardhat
// 模拟部署NFT市场合约
const nftMarket = await ethers.getContractFactory("CrayonNftMarket");
const market = await nftMarket.deploy();
await market.deployed(); // 等待市场合约部署
const marketAddress = market.address; // 获取市场合约部署地址
// 模拟部署NFT合约
const nftContract = await ethers.getContractFactory("CrayonNft");
const nft = await nftContract.deploy(marketAddress);
await nft.deployed();
const nftContractAddress = nft.address; // 获取NFT合约部署地址
ethers.js
matic
ethers
const auctionPrice = ethers.utils.parseUnits("100", "ether");
// 创建两个NFT Token
await nft.createToken("https://resources.crayon.dev/nfts/logo.jpg");
await nft.createToken("https://resources.crayon.dev/nfts/share.jpg");
// 将两个NFT 推送到市场进行交易
await market.createMarketItem(nftContractAddress, 1, auctionPrice, {
value: listingPrice,
});
await market.createMarketItem(nftContractAddress, 2, auctionPrice, {
value: listingPrice,
});
// 查询并返回未售出的NFT
let arrayItems = await market.fetchMarketItems();
arrayItems = await Promise.all(
arrayItems.map(async (i) => {
const tokenUri = await nft.tokenURI(i.tokenId);
return {
price: i.price.toString(),
tokenId: i.tokenId.toString(),
seller: i.seller,
owner: i.owner,
tokenUri,
};
})
);
console.log("未销售的NFT: ", arrayItems);
require("chai");
const { ethers } = require("hardhat");
describe("NFTMarket 测试", function () {
it("创建NFT并投入市场", async () => {
// 模拟部署NFT市场合约
const nftMarket = await ethers.getContractFactory("CrayonNftMarket");
const market = await nftMarket.deploy();
await market.deployed(); // 等待市场合约部署
const marketAddress = market.address; // 获取市场合约部署地址
// 模拟部署NFT合约
const nftContract = await ethers.getContractFactory("CrayonNft");
const nft = await nftContract.deploy(marketAddress);
await nft.deployed();
const nftContractAddress = nft.address; // 获取NFT合约部署地址
let listingPrice = await market.getListingPrice(); // 获取市场价格
listingPrice = listingPrice.toString();
// 将发布到市场的交易价格转为 ETH ,而不是 Gwei
const auctionPrice = ethers.utils.parseUnits("100", "ether");
// 创建两个NFT Token
await nft.createToken("https://resources.crayon.dev/nfts/logo.jpg");
await nft.createToken("https://resources.crayon.dev/nfts/share.jpg");
// 将两个NFT 推送到市场进行交易
await market.createMarketItem(nftContractAddress, 1, auctionPrice, {
value: listingPrice,
});
await market.createMarketItem(nftContractAddress, 2, auctionPrice, {
value: listingPrice,
});
/*
在现实世界的中,用户将通过Metamask等数字钱包与合约进行交互。
在测试环境中,将使用由Hardhat提供的本地地址进行交互
*/
const [_, buyerAddress] = await ethers.getSigners();
// 执行Token(即NFT)销售给另一个用户
await market
.connect(buyerAddress)
.createMarketSale(nftContractAddress, 1, { value: auctionPrice });
// 查询并返回未售出的NFT
let arrayItems = await market.fetchMarketItems();
arrayItems = await Promise.all(
arrayItems.map(async (i) => {
const tokenUri = await nft.tokenURI(i.tokenId);
return {
price: i.price.toString(),
tokenId: i.tokenId.toString(),
seller: i.seller,
owner: i.owner,
tokenUri,
};
})
);
console.log("未销售的NFT: ", arrayItems);
});
});
npx hardhat test
边栏推荐
- pycaret source code analysis: download dataset\Lib\site-packages\pycaret\datasets.py
- Web API 介绍和类型
- Handwritten a simple web server (B/S architecture)
- (26) About menu of the top menu of Blender source code analysis
- 【云驻共创】【HCSD大咖直播】亲授大厂面试秘诀
- Interview assault 69: TCP reliable?Why is that?
- 消息队列消息存储设计(架构实战营 模块八作业)
- 【Acwing】The 62nd Weekly Game Solution
- 一体化步进电机在无人机自动机场的应用
- [QNX Hypervisor 2.2 User Manual]9.16 system
猜你喜欢
Likou Binary Tree
[Cloud Residency Co-Creation] [HCSD Big Celebrity Live Broadcast] Personally teach the secrets of interviews in big factories
/etc/sysconfig/network-scripts configure the network card
SVN服务器搭建+SVN客户端+TeamCity集成环境搭建+VS2019开发
Classes and Objects: Above
日常--Kali开启SSH(详细教程)
【Acwing】The 62nd Weekly Game Solution
NIO编程
虹科分享|如何用移动目标防御技术防范未知因素
Google Earth Engine——Error: Image.clipToBoundsAndScale, argument ‘input‘: Invalid type的错误解决
随机推荐
Binary tree traversal non-recursive program -- using stack to simulate system stack
Pylint检查规则中文版
WindowInsetsControllerCompat is simple to use
Matlab / Arcgis处理nc数据
Advanced Algebra _ Proof _ Any matrix is similar to an upper triangular matrix
基于mysql的消息队列设计
新产品如何进行网络推广?
[AMEX] LGBM Optuna美国运通信用卡欺诈赛 kaggle
日常--Kali开启SSH(详细教程)
清华大学陈建宇教授团队 | 基于接触丰富机器人操作的接触安全强化学习框架
C# Rectangle基本用法和图片切割
【读书笔记->数据分析】02 数据分析准备
What is customer profile management?
Introduction to the five data types of Redis
Web API Introduction and Types
面试突击69:TCP 可靠吗?为什么?
/usr/local/bin和/usr/bin的区别
推荐系统:常用评价指标总结【准确率、精确率、召回率、命中率、(归一化折损累计增益)NDCG、平均倒数排名(MRR)、ROC曲线、AUC(ROC曲线下的面积)、P-R曲线、A/B测试】
To help the construction of digital government, the three parties of China Science and Technology build a domain name security system
Flutter教程之 02 Flutter 桌面程序开发入门教程运行hello world (教程含源码)