NFT 市场合约
概述
NFT 市场合约(NFTMarketplace
)实现了一个功能完整的 NFT 交易市场,支持 NFT 的上架、购买、取消上架等功能。该合约采用直接销售的模式,卖家可以设定固定价格,买家可以直接购买。
合约实现
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title NFT Marketplace
* @dev NFT 市场合约实现
*/
contract NFTMarketplace is ReentrancyGuard, Pausable, Ownable {
using SafeMath for uint256;
// 上架信息结构
struct Listing {
address seller; // 卖家地址
address nftContract; // NFT 合约地址
uint256 tokenId; // NFT ID
uint256 price; // 价格
address payToken; // 支付代币地址(address(0) 表示 ETH)
bool active; // 是否有效
}
// 报价信息结构
struct Offer {
address buyer; // 买家地址
uint256 price; // 报价
uint256 expiresAt; // 过期时间
bool active; // 是否有效
}
// 状态变量
mapping(uint256 => Listing) public listings;
mapping(uint256 => mapping(address => Offer)) public offers;
uint256 public listingIdCounter;
uint256 public platformFee;
address public feeRecipient;
mapping(address => bool) public approvedPayTokens;
// 事件定义
event ListingCreated(
uint256 indexed listingId,
address indexed seller,
address nftContract,
uint256 tokenId,
uint256 price,
address payToken
);
event ListingUpdated(
uint256 indexed listingId,
uint256 newPrice
);
event ListingCancelled(
uint256 indexed listingId,
address indexed seller
);
event ListingSold(
uint256 indexed listingId,
address indexed buyer,
uint256 price
);
event OfferCreated(
uint256 indexed listingId,
address indexed buyer,
uint256 price,
uint256 expiresAt
);
event OfferCancelled(
uint256 indexed listingId,
address indexed buyer
);
event OfferAccepted(
uint256 indexed listingId,
address indexed buyer,
uint256 price
);
/**
* @dev 构造函数
*/
constructor(
uint256 _platformFee,
address _feeRecipient
) {
require(_platformFee <= 1000, "Platform fee must be <= 10%");
require(_feeRecipient != address(0), "Invalid fee recipient");
platformFee = _platformFee;
feeRecipient = _feeRecipient;
}
/**
* @dev 创建上架
*/
function createListing(
address nftContract,
uint256 tokenId,
uint256 price,
address payToken
) external whenNotPaused nonReentrant returns (uint256) {
require(price > 0, "Price must be > 0");
require(
payToken == address(0) || approvedPayTokens[payToken],
"Payment token not approved"
);
IERC721 nft = IERC721(nftContract);
require(nft.ownerOf(tokenId) == msg.sender, "Not token owner");
require(
nft.isApprovedForAll(msg.sender, address(this)) ||
nft.getApproved(tokenId) == address(this),
"Not approved"
);
uint256 listingId = listingIdCounter++;
listings[listingId] = Listing({
seller: msg.sender,
nftContract: nftContract,
tokenId: tokenId,
price: price,
payToken: payToken,
active: true
});
emit ListingCreated(
listingId,
msg.sender,
nftContract,
tokenId,
price,
payToken
);
return listingId;
}
/**
* @dev 更新上架价格
*/
function updateListing(
uint256 listingId,
uint256 newPrice
) external nonReentrant {
require(newPrice > 0, "Price must be > 0");
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.sender == listing.seller, "Not seller");
listing.price = newPrice;
emit ListingUpdated(listingId, newPrice);
}
/**
* @dev 取消上架
*/
function cancelListing(uint256 listingId) external nonReentrant {
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.sender == listing.seller, "Not seller");
listing.active = false;
emit ListingCancelled(listingId, msg.sender);
}
/**
* @dev 购买 NFT
*/
function buyNFT(uint256 listingId) external payable nonReentrant {
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.sender != listing.seller, "Seller cannot buy");
if (listing.payToken == address(0)) {
require(msg.value >= listing.price, "Insufficient payment");
} else {
require(msg.value == 0, "ETH not accepted");
IERC20 payToken = IERC20(listing.payToken);
require(
payToken.allowance(msg.sender, address(this)) >= listing.price,
"Not approved"
);
require(
payToken.balanceOf(msg.sender) >= listing.price,
"Insufficient balance"
);
}
_executeSale(listingId, msg.sender, listing.price);
}
/**
* @dev 创建报价
*/
function createOffer(
uint256 listingId,
uint256 price,
uint256 duration
) external payable nonReentrant {
require(duration > 0 && duration <= 30 days, "Invalid duration");
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.sender != listing.seller, "Seller cannot offer");
require(price > 0, "Price must be > 0");
if (listing.payToken == address(0)) {
require(msg.value >= price, "Insufficient payment");
} else {
require(msg.value == 0, "ETH not accepted");
IERC20 payToken = IERC20(listing.payToken);
require(
payToken.allowance(msg.sender, address(this)) >= price,
"Not approved"
);
require(
payToken.balanceOf(msg.sender) >= price,
"Insufficient balance"
);
}
offers[listingId][msg.sender] = Offer({
buyer: msg.sender,
price: price,
expiresAt: block.timestamp + duration,
active: true
});
emit OfferCreated(listingId, msg.sender, price, block.timestamp + duration);
}
/**
* @dev 取消报价
*/
function cancelOffer(uint256 listingId) external nonReentrant {
Offer storage offer = offers[listingId][msg.sender];
require(offer.active, "Offer not active");
require(offer.buyer == msg.sender, "Not offer creator");
offer.active = false;
// 退还 ETH
if (listings[listingId].payToken == address(0)) {
(bool success, ) = msg.sender.call{value: offer.price}("");
require(success, "ETH transfer failed");
}
emit OfferCancelled(listingId, msg.sender);
}
/**
* @dev 接受报价
*/
function acceptOffer(
uint256 listingId,
address buyer
) external nonReentrant {
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.sender == listing.seller, "Not seller");
Offer storage offer = offers[listingId][buyer];
require(offer.active, "Offer not active");
require(block.timestamp <= offer.expiresAt, "Offer expired");
_executeSale(listingId, buyer, offer.price);
offer.active = false;
emit OfferAccepted(listingId, buyer, offer.price);
}
/**
* @dev 执行销售
*/
function _executeSale(
uint256 listingId,
address buyer,
uint256 price
) internal {
Listing storage listing = listings[listingId];
// 计算平台费用
uint256 fee = price.mul(platformFee).div(10000);
uint256 sellerProceeds = price.sub(fee);
// 转移支付
if (listing.payToken == address(0)) {
// 支付平台费用
if (fee > 0) {
(bool feeSuccess, ) = feeRecipient.call{value: fee}("");
require(feeSuccess, "Fee transfer failed");
}
// 支付卖家
(bool success, ) = listing.seller.call{value: sellerProceeds}("");
require(success, "Seller payment failed");
} else {
IERC20 payToken = IERC20(listing.payToken);
// 支付平台费用
if (fee > 0) {
require(
payToken.transferFrom(buyer, feeRecipient, fee),
"Fee transfer failed"
);
}
// 支付卖家
require(
payToken.transferFrom(buyer, listing.seller, sellerProceeds),
"Seller payment failed"
);
}
// 转移 NFT
IERC721(listing.nftContract).safeTransferFrom(
listing.seller,
buyer,
listing.tokenId
);
// 更新状态
listing.active = false;
emit ListingSold(listingId, buyer, price);
}
/**
* @dev 添加支付代币
*/
function addPayToken(address tokenAddress) external onlyOwner {
require(tokenAddress != address(0), "Invalid token address");
approvedPayTokens[tokenAddress] = true;
}
/**
* @dev 移除支付代币
*/
function removePayToken(address tokenAddress) external onlyOwner {
approvedPayTokens[tokenAddress] = false;
}
/**
* @dev 更新平台费用
*/
function updatePlatformFee(uint256 _platformFee) external onlyOwner {
require(_platformFee <= 1000, "Platform fee must be <= 10%");
platformFee = _platformFee;
}
/**
* @dev 更新费用接收地址
*/
function updateFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "Invalid fee recipient");
feeRecipient = _feeRecipient;
}
/**
* @dev 紧急暂停
*/
function pause() external onlyOwner {
_pause();
}
/**
* @dev 恢复运行
*/
function unpause() external onlyOwner {
_unpause();
}
/**
* @dev 接收 NFT 回调
*/
function onERC721Received(
address,
address,
uint256,
bytes memory
) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
}
功能说明
1. 市场管理
- 上架 NFT:支持固定价格上架
- 更新价格:允许卖家更新价格
- 取消上架:支持卖家取消上架
- 平台费用:支持收取交易费用
2. 交易机制
- 直接购买:按照固定价格购买
- 报价系统:支持买家出价
- 多币种支付:支持 ETH 和 ERC20 代币
- 自动结算:完成代币转移和资金分配
3. 报价管理
- 创建报价:买家可以提交报价
- 取消报价:买家可以取消报价
- 接受报价:卖家可以接受报价
- 过期机制:报价自动过期
4. 安全机制
- 重入保护:防止重入攻击
- 暂停功能:紧急情况可暂停合约
- 权限控制:管理功能仅限所有者
- 参数验证:严格的输入参数检查
使用示例
1. 上架 NFT
javascript
const price = ethers.utils.parseEther("1"); // 价格 1 ETH
const payToken = ethers.constants.AddressZero; // 使用 ETH 支付
await nftMarketplace.createListing(
nft.address, // NFT 合约地址
tokenId, // NFT ID
price,
payToken
);
2. 购买 NFT
javascript
await nftMarketplace.buyNFT(listingId, {
value: ethers.utils.parseEther("1")
});
3. 创建报价
javascript
const price = ethers.utils.parseEther("0.8"); // 报价 0.8 ETH
const duration = 86400; // 有效期 1 天
await nftMarketplace.createOffer(
listingId,
price,
duration,
{
value: price
}
);
4. 接受报价
javascript
await nftMarketplace.acceptOffer(listingId, buyerAddress);
最佳实践
1. 市场设置
- 合理的平台费用比例
- 严格的代币白名单
- 完善的价格验证
2. 安全考虑
- 定期检查合约状态
- 监控异常交易
- 做好应急预案
3. 交易管理
- 及时处理过期报价
- 监控交易完成情况
- 保持价格合理性
总结
该 NFT 市场合约实现了:
- 完整的交易功能
- 灵活的支付方式
- 严格的安全控制
- 可靠的资金管理
通过这个实现,可以安全、高效地进行 NFT 的交易和管理。
常见问题解答(FAQ)
1. 基本概念
Q: 什么是 NFT 市场? A: NFT 市场是一个去中心化的交易平台,允许用户上架、购买、出售和交易非同质化代币(NFT)。它为 NFT 创作者和收藏者提供了一个安全、透明的交易环境。
Q: NFT 市场与传统交易所有什么区别? A: 主要区别包括:
- 交易对象:NFT 是独特的非同质化资产
- 定价机制:通常采用固定价格或拍卖方式
- 版税机制:支持创作者持续获得收益
- 交易方式:一物一价,不可分割
- 元数据存储:需要处理链下数据
2. 功能相关
Q: 如何上架 NFT? A: 上架流程包括:
- 确保拥有 NFT 所有权
- 授权市场合约操作
- 设置售价和条件
- 提供必要的元数据
- 支付上架费用(如有)
Q: 如何设置合理的售价? A: 建议考虑以下因素:
- 作品的稀有度
- 创作者知名度
- 市场同类作品价格
- 历史交易数据
- 当前市场趋势
3. 安全相关
Q: 如何保护 NFT 安全? A: 采取以下措施:
- 严格的所有权验证
- 安全的授权机制
- 交易签名验证
- 防重入保护
- 紧急暂停功能
Q: 如何防止欺诈交易? A: 通过以下机制:
- 身份验证系统
- 信用评级机制
- 交易历史追踪
- 争议解决流程
- 社区监督机制
4. 费用相关
Q: 平台费用如何收取? A: 费用结构如下:
solidity
// 上架费用
listingFee = price * listingFeeRate / 10000
// 交易费用
tradingFee = price * tradingFeeRate / 10000
// 创作者版税
royalty = price * royaltyRate / 10000
Q: 版税如何分配? A: 版税分配机制:
- 自动计算应付版税
- 直接支付给创作者
- 支持多级分成
- 可配置分成比例
- 透明的分配记录
5. 技术相关
Q: 如何处理元数据? A: 元数据管理方案:
- 链下存储(IPFS/Arweave)
- 链上索引
- 缓存机制
- 数据验证
- 更新机制
Q: 如何优化 Gas 费用? A: 优化策略包括:
- 批量处理交易
- 优化数据结构
- 减少存储操作
- 使用事件代替存储
- 实现 EIP-2981
6. 最佳实践
Q: 如何提高交易成功率? A: 建议采取:
- 合理定价策略
- 完整的作品信息
- 优质的展示材料
- 活跃的社区互动
- 良好的买家体验
Q: 如何管理 NFT 集合? A: 集合管理建议:
- 创建系列标准
- 设置访问权限
- 管理元数据
- 跟踪交易数据
- 维护社区关系
7. 错误处理
Q: 常见错误及解决方案? A: 主要错误类型:
"Not owner"
: 检查 NFT 所有权"Not approved"
: 确认授权状态"Invalid price"
: 修正定价设置"Insufficient funds"
: 确保资金充足"Already listed"
: 检查上架状态
Q: 如何处理交易失败? A: 失败处理机制:
- 自动回滚交易
- 退还支付金额
- 恢复 NFT 状态
- 记录错误原因
- 通知相关方
8. 升级和维护
Q: 如何升级市场功能? A: 升级策略:
- 使用代理合约
- 模块化设计
- 版本控制
- 平滑迁移
- 向后兼容
Q: 如何维护市场健康? A: 维护措施:
- 监控交易活动
- 处理用户反馈
- 更新安全参数
- 优化用户体验
- 社区治理参与
9. 集成和互操作
Q: 如何与其他协议集成? A: 集成方案:
- 标准接口兼容
- 跨协议互操作
- 聚合器支持
- 流动性共享
- 数据互通
Q: 如何实现跨链功能? A: 跨链实现:
- 桥接协议集成
- 跨链消息传递
- 资产映射
- 状态同步
- 安全验证
10. 数据和分析
Q: 如何追踪市场数据? A: 数据追踪方法:
- 事件监听
- 价格追踪
- 交易量分析
- 用户行为分析
- 市场趋势报告
Q: 如何使用市场数据? A: 数据应用:
- 定价参考
- 市场预测
- 风险评估
- 用户画像
- 运营决策