实现的基本功能
- (一)
- 发行一个符合ERC20标准的测试Token,要求如下:
- 总量::1亿
- 精度:18
- 名称:Fake USDT in CBI
- 简称:cUSDT
- 发行一个符合ERC20标准的测试Token,要求如下:
- (二)
- 发行一个符合ERC721标准的测试Token,要求如下:
- 名称:NFTMarketplace
- 简称:NFTM
- tokenid自增
- 用户在网页上传图片提交之后自动发行给用户
- 发行一个符合ERC721标准的测试Token,要求如下:
- (三)
- 开发一个NFT交易市场,功能如下:
- 用户在创建NFT时可以指定价格(以cUSDT计价)
- NFT所有者可以修改上架的NFFT价格
- NFT信息上传到IPFS
- NFT所有者可以下架市场里自己的NFT
- 用户可以在NFT市场以一定的价格购买NFT
- 开发一个NFT交易市场,功能如下:
实现的详细步骤
初始化hardhat工程
- 前提是要安装好node.js。
- 在自己新建的项目目录下打开终端
- 使用
npm install --save-dev hardhat
命令安装hardhat - 输入
npx hardhat init
命令进行初始化
- 然后就搭建好了我们的一个hardhat框架
ERC20
-
然后输入
code .
命令在vscode打开该文件夹,在contracts文件夹下新建一个erc20-usdt.sol的合约文件,该文件夹下的初始合约文件可以删除,然后打开https://wizard.openzeppelin.com/#erc721
网址复制合约代码
-
然后在vscode按照开发要求修改代码,修改后代码如下
-
在终端输入
npm install @openzeppelin/contracts
命令安装第三方的库 -
在remix里面部署合约测试,关于本地文件连接remix教程前面文章详细讲过
ERC721
- 还是从之前的网址复制代码
- 新建erc721-nft.sol合约文件,将代码复制到该文件,然后编译测试一下
NFT交易市场
- 新建一个nft-market.sol文件
- 根据要求写合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
contract Market {
//将erc20初始化为一个IERC20类型的实例,但具体的地址尚未赋值,后续的构造函数会使用传入的参数来设置erc20,从而与实际的ERC20代币合约进行交互
IERC20 public erc20;
IERC721 public erc721;
bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;
//包含代币售卖地址,代币Id,代币价格
struct Order {
address seller;
uint256 tokenId;
uint256 price;
}
mapping(uint256 => Order) public orderOfId; //根据tokenId查询Order
Order[] public orders; //定义一个Order类型的数组存放所有的Order
mapping (uint256 => uint256) public idToOrderIndex; //根据tokenId查询在数组中的索引
event Deal(address seller, address buyer, uint256 tokenId, uint256 price); //交易信息
event NewOrder(address seller, uint256 tokenId, uint256 price); //新的卖家信息
event PriceChanged(address seller, uint256 tokenId, uint256 previousPrice, uint256 price); //价格修改信息
event OrderCancelled(address seller, uint256 tokenId); //卖家退出交易市场信息
//获取ERC20和ERC721的合约地址,这俩合约地址在部署合约时,在详细信息里面可以找到,合约地址不能时零地址
constructor(address _erc20, address _erc721) {
require(_erc20 != address(0), "zero address");
require(_erc721 != address(0), "zero address");
//将传入的地址_erc20转换为IERC20类型的实例,并将其赋值给变量erc20
erc20 = IERC20(_erc20);
erc721 = IERC721(_erc721);
}
//购买函数
function buy(uint256 _tokenId) external {
//获取该tokenId的卖家地址
address seller = orderOfId[_tokenId].seller;
//获取该tokenI的售卖价格
uint256 price = orderOfId[_tokenId].price;
//买方地址即当前调用合约的地址
address buyer = msg.sender;
//调用ERC20里面的授权转账函数进行转账
/*关于这个授权转账函数,最开始有一点疑惑觉得没有授权市场哪来的权限转钱,后来想明白了,授权转账函数应该是购买者给市场合约授权才能有权限去转走购买者的币给卖家,具体这个该函数我在ERC20学习里面讲过
假如A授权给B一定量的币允许它使用,B要给C转账,因此就是B调用合约,而里面的from就是A,to就是C,即使A给B授权,但是币仍然在A手中,因此这里的买家相当于
A,市场合约相当于B,而卖家相当于C,因此买家2需要给市场合约授权才能完成购买,,即给市场合约approve一下
*/
require(erc20.transferFrom(buyer, seller, price), "transfer not successful");
//调用了ERC721代币合约的safeTransferFrom函数,将指定的ERC721代币(由_tokenId标识)从当前合约地址(address(this))安全地转移到买家账户(buyer)。
erc721.safeTransferFrom(address(this), buyer, _tokenId);
emit Deal(seller, buyer, _tokenId, price); //释放交易信息
}
//下架函数
function cancelOrder(uint256 _tokenId) external {
//获取卖家地址
address seller = orderOfId[_tokenId].seller;
//查看当前地址是不是卖家,因为商品只能由卖家自己下架
require(msg.sender == seller, "not seller");
erc721.safeTransferFrom(address(this),seller, _tokenId);
emit OrderCancelled(seller, _tokenId);
}
//改价函数
function changePrice(uint256 _tokenId, uint256 _price) external {
address seller = orderOfId[_tokenId].seller;
require(msg.sender == seller, "not seller");
uint256 previousPrice = orderOfId[_tokenId].price;
orderOfId[_tokenId].price = _price;
//注意,不仅要修改Order的价格,orders数组里面存储的该地址的代币价格也需要修改
//memory修改不会存储在链上,用于存储临时数据,storage修改会存储在链上,用于永久保存
Order storage order = orders[idToOrderIndex[_tokenId]];
order.price = _price;
emit PriceChanged(seller, _tokenId, previousPrice, _price);
}
//上架函数
//用于处理ERC721代币的接收事件,当其它合约或用户向该合约发送ERC721代币时,会触发该函数,可以在该函数中编写逻辑来处理接收到的代币
function onERC721Received(
address operator, //执行操作的地址
address from, //发送代币的地址
uint256 tokenId, //接收到的代币的唯一标识符
bytes calldata data //附加数据,可以是任意字节数组
) external returns (bytes4) {
//调用格式转换函数得到价格
uint256 price= toUint256(data, 0);
require(price > 0, "price must be greater than 0");
//上架
orders.push(Order(from, tokenId, price));
orderOfId[tokenId] = Order(from, tokenId, price);
idToOrderIndex[tokenId] = orders.length - 1;
emit NewOrder(from, tokenId, price);
//返回该值,这样发送方的合约就可以指导接收方是否已经正确处理了代币转移
return MAGIC_ON_ERC721_RECEIVED;
}
//下架函数
function removeOrder(uint256 _tokenId) internal {
//先从数组中把他删除掉
uint256 index = idToOrderIndex[_tokenId];
uint256 lastIndex = orders.length - 1;
if(index != lastIndex){
Order storage lastOrder = orders[lastIndex];
orders[index] = lastOrder;
idToOrderIndex[lastOrder.tokenId] = index;
}
orders.pop();
//从mapping里面删除
delete orderOfId[_tokenId];
delete idToOrderIndex[_tokenId];
}
//格式转换函数
function toUint256(bytes memory _bytes, uint256 _start) public pure returns (uint256) {
require(_start + 32 >= _start,"Market: toUint256_overflow");
require(_bytes.length >= _start + 32,"Market: toUint256_outOfBounds");
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
//获取数组订单长度
function getOrderLength() external view returns (uint256) {
return orders.length;
}
//获取所有上架的NFT
function getAllNFTs() external view returns (Order[] memory) {
return orders;
}
//获取用户自己上架的NFT
function getMyNFTs() external view returns (Order[] memory) {
Order[] memory myOrders = new Order[](orders.length);
uint256 count = 0;
for (uint256 i = 0; i< orders.length; i++) {
if (orders[i].seller == msg.sender) {
myOrders[count] = orders[i];
count ++;
}
}
return myOrders;
}
}
编译测试
-
先编译部署ERC20,这个直接编译部署就行
-
然后编译部署ERC721
-
编译部署NFT合约
-
在ERC721部署的合约里给自己mint几个NFT
-
查看是否mint成功,我们可以看到地址是我们刚刚mint的地址,因为该地址是第一个被mint的,并且我刚刚给该地址mint了三个NFT,因此该地址有tokenId为0,1,2的NFT,输入0或者1或者2都是该地址,成功!
-
上架!NFT价格参数格式在
https://stackoverflow.com/questions/63252057/how-to-use-bytestouint-function-in-solidity-the-one-with-assembly
这个里面找
-
在nft合约里验证是否上架
-
再去ERC721里面上架代币,一样的,修改下tokenId就可以,因为在ERC721里面代币是唯一的,所以每个代币的tokenId都不同
-
获取所有上架的NFT信息
-
用户获取自己上架了几个NFT,在nft合约里面查看
-
在刚开始我们已经给A地址mint了NFT,现在给B地址mintNFT,注意操作地址必须是A地址,即部署合约的地址,否则无法mint
-
然后看一下我们的NFT拥有者地址都是谁
-
现在将B地址的NFT上架,记住谁的NFT上架就要谁操作,因此这个时候是B地址调用合约函数
-
然后我们可以看到NFT上架成功
-
然后修改上架的NFT价格,注意调用合约的地址的切换,即谁的NFT谁修改,你不能改别人的NFT价格,同理别人也不能改你的NFT价格,不然整个交易市场就乱套啦,这里我修改B地址的tokenId为0的NFT
-
查看价格是否修改成功,我们这里看到价格已经修改成功啦
-
然后下架NFT,我目前A地址有tokenId为0、1的NFT,B地址有tokenId为3,4的NFT,现在我要下架tokenId为0的NFT,也是,需要自己下架
-
然后查看自己上架的NFT即可发现下架成功
-
然后去购买NFT,这个函数大致解释我在代码里标注了,需要授权才能完成购买,因此我们先去erc20合约给市场合约授权(approve)
-
现在可以去购买NFT啦,假设A地址是买家哎,现在是A地址调用合约去买,买B地址的tokenId为4的NFT,购买成功!!!
-
现在查看tokenId为4的NFT是否已经在A地址手中,我们可以看到该NFT的拥有者地址已经变成了A地址
-
💪未完待续~
-
课程可以去哔哩哔哩上搜梁培利,很好的老师,也是我的老师😊。