文章整体目录
二、 NFT交易市场合约开发
三、 NFT交易市场后端开发
四、 NFT交易市场前端开发
文章目录
一 、ERC是什么?
ERC 全称是“Ethereum Request for Comment”,表示以太坊的意见征求稿,ERC 中包含技术和组织等注意事项及标准。这套标准其实不光由以太坊官方提出,还由一些以太坊爱好者提出。是以太坊生态系统中被广泛使用的关键标准。
代币(token)标准
- ERC-20 - 同质化(可互换)代币的标准接口,比如投票代币、质押代币或虚拟货币。
- ERC-721 - 非同质化代币的标准接口,比如艺术作品或歌曲的契约。
- ERC-777 - 关于 ERC-20 的代币标准改进。
- ERC-1155 - 一个能包括同质化和非同质化资产的代币标准。
openzeppelin 智能合约库
官网:https://www.openzeppelin.com/
GitHub:https://github.com/OpenZeppelin/openzeppelin-contracts
OpenZeppelin 是一个使用以太坊智能合约语言 Solidity 进行构建的开发框架,可以简化智能合约和 Dapp 的开发。
OpenZeppelin合约和库已成为行业标准,其开源代码模板经历了以太坊及其他区块链的实战考验,帮助开发者最大限度降低风险。OpenZeppelin代码包括使用度最高的ERC标准及拓展部署,已被社区在各类指南以及操作教程中大量使用。
Contract Wizard
OpenZeppelin开发了一种基于网络的线上智能合约交互式工具,它可能是使用OpenZeppelin代码编写智能合约最简单快捷的方式。这一工具称为Contracts Wizard。
二、hardhat工程的安装
- 选择一个空文件
- 进入命令行
npm install --save-dev hardhat
- 启动项目
npx hardhat
- 启动节点
npx hardhat node
其他的命令请自行阅读README.md文件
三、ERC-20合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Xing is ERC20 {
constructor() ERC20("fake usdt in cbi", "cUSDT") {
_mint(msg.sender, 1 * 10**8 * 10**18); //发行一个亿的币
}
}
四、ERC-721合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFTM is ERC721, ERC721Enumerable, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("NFTM", "NFTM")
Ownable(initialOwner)
{}
function _baseURI() internal pure override returns (string memory) {
return "https://sameple.com/";
}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
}
// The following functions are overrides required by Solidity.
function _update(
address to,
uint256 tokenId,
address auth
) internal override(ERC721, ERC721Enumerable) returns (address) {
return super._update(to, tokenId, auth);
}
function _increaseBalance(address account, uint128 value)
internal
override(ERC721, ERC721Enumerable)
{
super._increaseBalance(account, value);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
以上代码均由https://docs.openzeppelin.com/contracts/5.x/wizard网址生成
五、NFTMarket交易市场的开发
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC20.sol";
// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
contract Market {
IERC20 public erc20; //不写成address是为了以后可以直接调用合约的方法,不用显示转换
IERC721 public erc721; //不写成address是为了以后可以直接调用合约的方法,不用显示转换
//用户上架NTF过程中,hook钩子验证的一个验证码(类似于)
bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;
struct Order {
address seller;
uint256 tokenId;
uint256 price;
}
/*将每个tokenId(非同质代币的唯一标识符)映射到一个 Order 结构。
举例说明:如果某个用户想要出售自己的非同质代币,他会创建一个 Order 结构,
然后将其与相应的 tokenId 关联。
其他用户可以查询 orderOfId 来查看与特定 tokenId 相关联的订单。 */
mapping(uint256 => Order) public orderOfId; //token id to order
/**
如果有多个用户分别出售不同的非同质代币,
这些订单将存储在 orders 数组中,
以便买家可以查看所有可用的订单。 */
Order[] public orders;
/**
将每个 tokenId 映射到相应的订单在 orders 数组中的索引位置。
这是为了方便后续对数组中订单的快速访问
当一个新订单被创建并添加到 orders 数组时,
idToOrderIndex 将被更新,使得可以通过 tokenId 快速找到订单在数组中的位置 */
mapping(uint256 => uint256) public idToOrderIndex; // token id to index in orders
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);
constructor(address _erc20, address _erc721) {
require(_erc20 != address(0), "zero address");
require(_erc721 != address(0), "zero address");
erc20 = IERC20(_erc20);
erc721 = IERC721(_erc721);
}
function buy(uint256 _tokenId) external {
address seller = orderOfId[_tokenId].seller;
address buyer = msg.sender;
uint256 price = orderOfId[_tokenId].price;
require(
erc20.transferFrom(buyer, seller, price),
"transfer not successful"
); //存在返回值的隐藏坑
erc721.safeTransferFrom(address(this), buyer, _tokenId);
removeOrder(_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);
removeOrder(_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里的价格
orderOfId[_tokenId].price = _price;
//修改order里的价格
Order storage order = orders[idToOrderIndex[_tokenId]];
order.price = _price;
emit PriceChanged(seller, _tokenId, previousPrice, _price);
}
/**
要删除的元素与最后一个元素交换
*/
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();
delete orderOfId[_tokenId];
delete idToOrderIndex[_tokenId];
}
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 toUint256(bytes memory _bytes, uint256 _start)
public
pure
returns (uint256)
{
require(_start + 32 >= _start, "Market:toUint256_overflow");
require(_bytes.length >= _start + 32, "Market: toUint256_out0fBounds");
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function isListed(uint256 _tokenId) public view returns (bool) {
return orderOfId[_tokenId].seller != address(0);
}
function getOrderLength() external view returns (uint256) {
return orders.length;
}
function getAllNFTs() external view returns (Order[] memory) {
return orders;
}
/**
如果没被上架,那么格式就如下,地址为0
tuple(address,uint256,uint256)[]: 0x0000000000000000000000000000000000000000,0,0
*/
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;
}
}
重点!!
hook钩子,调用safeTransFrom会验证to地址是不是合约地址,若为合约地址,就会尝试调用onERC721Received方法,并且要求返回值为之前约定好的值(Bytes4)。
用户在调用NTF market(合约地址)的safeTransFrom,就会有个钩子自动调用onERC721Received。我们就需要再onERC721Received完成帮助用户上架NTF的功能。
用户和market两头是有个对接的,我们将价格以bytes4编码到safeTransFrom的data属性里去,market会自动解析为unit256的形式并上架
有了这个方法,NTF才能安全的被传送到_to地址
Finally
第一次写博客,有什么问题欢迎大家指出
有涉及到侵权请及时联系我!
接下来请浏览继续学习Dapp项目的开发(三)