跳转到内容

NFT Marketplace

NFT Marketplace

本页内容正在整理中,欢迎贡献

概述

NFT Marketplace 允许用户上架、购买和竞拍 NFT。本文介绍如何实现一个简单但功能完整的 NFT 交易市场,涵盖固定价格出售、版税分配和托管模式。

主要内容

Marketplace 合约架构

NFT Marketplace
├── 上架(List) 卖家授权 Marketplace 并设置价格
├── 取消上架 卖家取消销售
├── 购买(Buy) 买家支付 ETH,Marketplace 转移 NFT
├── 竞拍(Bid) 竞价模式(可选)
└── 版税分配 从销售额中扣除版税给创作者

简单固定价格 Marketplace

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
contract NFTMarketplace is ReentrancyGuard, Ownable {
struct Listing {
address seller;
uint256 price;
bool active;
}
// nftContract => tokenId => Listing
mapping(address => mapping(uint256 => Listing)) public listings;
uint256 public platformFeeBps = 250; // 2.5%(基于 10000)
uint256 private _platformFees;
event Listed(address indexed nft, uint256 indexed tokenId, address seller, uint256 price);
event Sold(address indexed nft, uint256 indexed tokenId, address buyer, uint256 price);
event Cancelled(address indexed nft, uint256 indexed tokenId);
constructor() Ownable(msg.sender) {}
// 上架 NFT
function listNFT(
address nftContract,
uint256 tokenId,
uint256 price
) external {
require(price > 0, "Price must be > 0");
IERC721 nft = IERC721(nftContract);
require(nft.ownerOf(tokenId) == msg.sender, "Not owner");
require(
nft.isApprovedForAll(msg.sender, address(this)) ||
nft.getApproved(tokenId) == address(this),
"Marketplace not approved"
);
listings[nftContract][tokenId] = Listing({
seller: msg.sender,
price: price,
active: true
});
emit Listed(nftContract, tokenId, msg.sender, price);
}
// 购买 NFT
function buyNFT(
address nftContract,
uint256 tokenId
) external payable nonReentrant {
Listing storage listing = listings[nftContract][tokenId];
require(listing.active, "Not listed");
require(msg.value >= listing.price, "Insufficient payment");
listing.active = false;
uint256 salePrice = listing.price;
address seller = listing.seller;
// 计算平台费
uint256 platformFee = (salePrice * platformFeeBps) / 10000;
uint256 royaltyAmount = 0;
// 计算版税(ERC-2981)
if (IERC165(nftContract).supportsInterface(type(IERC2981).interfaceId)) {
(address royaltyReceiver, uint256 royalty) = IERC2981(nftContract)
.royaltyInfo(tokenId, salePrice);
if (royaltyReceiver != address(0) && royaltyReceiver != seller) {
royaltyAmount = royalty;
payable(royaltyReceiver).transfer(royaltyAmount);
}
}
// 转账给卖家
uint256 sellerProceeds = salePrice - platformFee - royaltyAmount;
_platformFees += platformFee;
payable(seller).transfer(sellerProceeds);
// 转移 NFT
IERC721(nftContract).safeTransferFrom(seller, msg.sender, tokenId);
// 退还多余 ETH
if (msg.value > salePrice) {
payable(msg.sender).transfer(msg.value - salePrice);
}
emit Sold(nftContract, tokenId, msg.sender, salePrice);
}
// 取消上架
function cancelListing(address nftContract, uint256 tokenId) external {
Listing storage listing = listings[nftContract][tokenId];
require(listing.active, "Not listed");
require(listing.seller == msg.sender, "Not seller");
listing.active = false;
emit Cancelled(nftContract, tokenId);
}
// 修改价格
function updatePrice(
address nftContract,
uint256 tokenId,
uint256 newPrice
) external {
Listing storage listing = listings[nftContract][tokenId];
require(listing.active, "Not listed");
require(listing.seller == msg.sender, "Not seller");
require(newPrice > 0, "Price must be > 0");
listing.price = newPrice;
}
// 提取平台费
function withdrawFees() external onlyOwner {
uint256 amount = _platformFees;
_platformFees = 0;
payable(owner()).transfer(amount);
}
}

前端集成

import { useWriteContract, useReadContract } from "wagmi";
import { parseEther, formatEther } from "viem";
// 上架 NFT
function ListNFTButton({ nftAddress, tokenId, priceEth }: {
nftAddress: `0x${string}`,
tokenId: bigint,
priceEth: string
}) {
const { writeContract: approveNFT } = useWriteContract();
const { writeContract: listNFT } = useWriteContract();
const handleList = async () => {
// Step 1: 授权 Marketplace
await approveNFT({
address: nftAddress,
abi: ERC721_ABI,
functionName: "setApprovalForAll",
args: [MARKETPLACE_ADDRESS, true],
});
// Step 2: 上架
await listNFT({
address: MARKETPLACE_ADDRESS,
abi: MARKETPLACE_ABI,
functionName: "listNFT",
args: [nftAddress, tokenId, parseEther(priceEth)],
});
};
return <button onClick={handleList}>上架 NFT</button>;
}

主流 NFT 市场参考

平台特色版税支持
OpenSea最大综合市场可选(争议)
Blur专业交易者,零版税选项可选
Magic Eden多链支持支持
Reservoir聚合 API取决于来源

深入阅读