前言
本文实现一个简单版的NFT交易所,主要包含上架、下架、更新价格、购买NFT等相关功能
NFT合约说明
关于NFT合约的开发、测试、部署具体实现,可以查看另一篇:快速实现一个标准的NFT合约(实操篇)
NFT交易所合约
合约说明具备上架、下架、更新价格、购买NFT
java
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import "hardhat/console.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "./NFT.sol";//实现的nft的合约
contract NFTSwap is IERC721Receiver {
event List(
address indexed seller,
address indexed nftAddr,
uint256 indexed tokenId,
uint256 price
);
event Purchase(
address indexed buyer,
address indexed nftAddr,
uint256 indexed tokenId,
uint256 price
);
event Revoke(
address indexed seller,
address indexed nftAddr,
uint256 indexed tokenId
);
event Update(
address indexed seller,
address indexed nftAddr,
uint256 indexed tokenId,
uint256 newPrice
);
receive() external payable {}//解决没有接受函数的问题
// 定义order结构体
struct Order {
address owner;
uint256 price;
}
// NFT Order映射
mapping(address => mapping(uint256 => Order)) public nftList;
fallback() external payable {}
// 挂单: 卖家上架NFT,合约地址为_nftAddr,tokenId为_tokenId,价格_price为以太坊(单位是wei)
function list(address _nftAddr, uint256 _tokenId, uint256 _price) public {
IERC721 _nft = IERC721(_nftAddr); // 声明IERC721接口合约变量
require(_nft.getApproved(_tokenId) == address(this), "Need Approval"); // 合约得到授权
require(_price > 0); // 价格大于0
Order storage _order = nftList[_nftAddr][_tokenId]; //设置NFT持有人和价格
_order.owner = msg.sender;
_order.price = _price;
// 将NFT转账到合约
_nft.safeTransferFrom(msg.sender, address(this), _tokenId);
// 释放List事件
emit List(msg.sender, _nftAddr, _tokenId, _price);
}
// 购买: 买家购买NFT,合约为_nftAddr,tokenId为_tokenId,调用函数时要附带ETH
function purchase(address _nftAddr, uint256 _tokenId) public payable {
Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order
require(_order.price > 0, "Invalid Price"); // NFT价格大于0
require(msg.value >= _order.price, "Increase price"); // 购买价格大于标价
// 声明IERC721接口合约变量
IERC721 _nft = IERC721(_nftAddr);
require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中
// 将NFT转给买家
_nft.safeTransferFrom(address(this), msg.sender, _tokenId);
// 将ETH转给卖家
payable(_order.owner).transfer(_order.price);
// 多余ETH给买家退款
if (msg.value > _order.price) {
payable(msg.sender).transfer(msg.value - _order.price);
}
// 释放Purchase事件
emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price);
delete nftList[_nftAddr][_tokenId]; // 删除order
}
// 撤单: 卖家取消挂单
function revoke(address _nftAddr, uint256 _tokenId) public {
Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order
require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起
// 声明IERC721接口合约变量
IERC721 _nft = IERC721(_nftAddr);
require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中
// 将NFT转给卖家
_nft.safeTransferFrom(address(this), msg.sender, _tokenId);
delete nftList[_nftAddr][_tokenId]; // 删除order
// 释放Revoke事件
emit Revoke(msg.sender, _nftAddr, _tokenId);
}
// 调整价格: 卖家调整挂单价格
function update(
address _nftAddr,
uint256 _tokenId,
uint256 _newPrice
) public {
require(_newPrice > 0, "Invalid Price"); // NFT价格大于0
Order storage _order = nftList[_nftAddr][_tokenId]; // 取得Order
require(_order.owner == msg.sender, "Not Owner"); // 必须由持有人发起
// 声明IERC721接口合约变量
IERC721 _nft = IERC721(_nftAddr);
require(_nft.ownerOf(_tokenId) == address(this), "Invalid Order"); // NFT在合约中
// 调整NFT价格
_order.price = _newPrice;
// 释放Update事件
emit Update(msg.sender, _nftAddr, _tokenId, _newPrice);
}
// 实现{IERC721Receiver}的onERC721Received,能够接收ERC721代币
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
NFT交易所测试
javascript
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("NFTSwap",function(){
let nft;//合约
let nftswap;//合约
let addr1;
let addr2;
let firstAccount//第一个账户
let secondAccount//第二个账户
let mekadata='ipfs://QmQT8VpmWQVhUhoDCEK1mdHXaFaJ3KawkRxHm96GUhrXLB';//mekadata关于说明nft相关描述,属性的json文件
let mekadata1="ipfs://QmXzbsbjpWpbSGJkgGzmk6r6HLz1nvjpEtjFR6bVhMh3U9";//同上
beforeEach(async function(){
await deployments.fixture(["nft","nftswap"]);//部署nft和nft交易所
[addr1,addr2]=await ethers.getSigners();//获取账号(说明:这样获取的账号支持签名操作,例如:如果使用secondAccount就会报错VoidSigner cannot sign transactions)
firstAccount=(await getNamedAccounts()).firstAccount;
secondAccount=(await getNamedAccounts()).secondAccount;
const nftDeployment = await deployments.get("BoykaNFT");
nft = await ethers.getContractAt("BoykaNFT",nftDeployment.address);//已经部署的合约交互
const nftswapDeployment = await deployments.get("NFTSwap");
nftswap = await ethers.getContractAt("NFTSwap",nftswapDeployment.address);//已经部署的合约交互
})
describe("nftswap",function(){
it("把nft授权给交易所swap",async ()=>{
//铸造两个nft
await nft.safeMint(firstAccount,mekadata);
console.log(await nft.ownerOf(0))//查看持有者
await nft.safeMint(firstAccount,mekadata1);
console.log(await nft.ownerOf(1))//同上
//把铸造的nft授权给nft交易所
await nft.approve(nftswap.address,0);
await nft.approve(nftswap.address,1);
//上架nft
//把nft放到交易所 参数说明:nft的合约地址 ,nftid,nft的价格
await nftswap.list(nft.address,0,ethers.utils.parseEther("1"));
await nftswap.list(nft.address,1,ethers.utils.parseEther("1"));
//查看是否上架成功
console.log('上架成功',await nftswap.nftList(nft.address,0))
//如果不存在的nft会返回owner: '0x0000000000000000000000000000000000000000',
console.log("查看不存在的nft",await nftswap.nftList(nft.address,3))//不存在的nft
//更新nft的价格
await nftswap.update(nft.address,0,ethers.utils.parseEther("2"));//把nft的价格改成2eth
console.log("查看更新的价格",await nftswap.nftList(nft.address,0))
await nftswap.update(nft.address,1,ethers.utils.parseEther("23"));//同上
console.log("查看更新的价格",await nftswap.nftList(nft.address,1))
//下架
//把nftid为1的nft下架
await nftswap.revoke(nft.address,1);
//返回owner为0x0000000000000000000000000000000000000000说明下架成功
console.log('下架成功',await nftswap.nftList(nft.address,1))
//购买nft
console.log(secondAccount)
//切换账号到addr2使用await ethers.getSigners() 添加ether足够的值
//说明参数:1.nft合约地址2.nft的id3.addr2的msg.value的eth,要大于nft的价格当前addr2的值为3
await nftswap.connect(addr2).purchase(nft.address,0,{value:ethers.utils.parseEther("3")});
console.log('所有权时addr2',await nft.ownerOf(0));//返回的账号地址时addr2说明addr2购买成功了
})
})
})
# 测试指令
# npx hardhat test ./test/xxx.js
NFT交易所部署
javascript
module.exports = async function ({getNamedAccounts,deployments}) {
const firstAccount= (await getNamedAccounts()).firstAccount;
const {deploy,log} = deployments;
const NftSwap=await deploy("NFTSwap",{
from:firstAccount,
args: [],//参数
log: true,
})
console.log("nftswap合约",NftSwap.address)
}
module.exports.tags = ["all","nftswap"];
# 部署指令
# npx hardhat deploy
总结
以上就是简洁版NFT交易所的整个实现过程,此交易所具备上架、下架、改价、购买的功能和一些验证的方法。