// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFTMarketplace is ReentrancyGuard, Pausable, Ownable, IERC721Receiver {
struct Listing {
address seller;
address nftContract;
uint256 tokenId;
uint256 price;
bool isActive;
}
struct Auction {
address seller;
address nftContract;
uint256 tokenId;
uint256 startingPrice;
uint256 highestBid;
address highestBidder;
uint256 endTime;
bool isActive;
}
mapping(uint256 => Listing) public listings;
mapping(uint256 => Auction) public auctions;
mapping(address => uint256) public pendingWithdrawals;
uint256 public listingFee = 0.001 ether;
uint256 public auctionFee = 0.002 ether;
uint256 public marketplaceFee = 250; // 2.5% (250/10000)
uint256 public nextListingId;
uint256 public nextAuctionId;
event NFTListed(uint256 indexed listingId, address indexed seller, address indexed nftContract, uint256 tokenId, uint256 price);
event NFTPurchased(uint256 indexed listingId, address indexed buyer, uint256 price);
event NFTDelisted(uint256 indexed listingId);
event AuctionCreated(uint256 indexed auctionId, address indexed seller, address indexed nftContract, uint256 tokenId, uint256 startingPrice, uint256 endTime);
event BidPlaced(uint256 indexed auctionId, address indexed bidder, uint256 amount);
event AuctionEnded(uint256 indexed auctionId, address indexed winner, uint256 amount);
constructor() Ownable(msg.sender) {}
// List NFT for sale
function listNFT(address _nftContract, uint256 _tokenId, uint256 _price) external payable nonReentrant whenNotPaused {
require(msg.value >= listingFee, "Insufficient listing fee");
require(_price > 0, "Price must be greater than 0");
IERC721 nft = IERC721(_nftContract);
require(nft.ownerOf(_tokenId) == msg.sender, "Not the owner");
require(nft.getApproved(_tokenId) == address(this), "Marketplace not approved");
uint256 listingId = nextListingId++;
listings[listingId] = Listing({
seller: msg.sender,
nftContract: _nftContract,
tokenId: _tokenId,
price: _price,
isActive: true
});
nft.safeTransferFrom(msg.sender, address(this), _tokenId);
emit NFTListed(listingId, msg.sender, _nftContract, _tokenId, _price);
}
// Purchase listed NFT
function purchaseNFT(uint256 _listingId) external payable nonReentrant {
Listing storage listing = listings[_listingId];
require(listing.isActive, "Listing not active");
require(msg.value >= listing.price, "Insufficient payment");
listing.isActive = false;
uint256 fee = (listing.price * marketplaceFee) / 10000;
uint256 sellerProceeds = listing.price - fee;
pendingWithdrawals[listing.seller] += sellerProceeds;
pendingWithdrawals[owner()] += fee;
if (msg.value > listing.price) {
pendingWithdrawals[msg.sender] += msg.value - listing.price;
}
IERC721(listing.nftContract).safeTransferFrom(address(this), msg.sender, listing.tokenId);
emit NFTPurchased(_listingId, msg.sender, listing.price);
}
// Create auction
function createAuction(address _nftContract, uint256 _tokenId, uint256 _startingPrice, uint256 _duration) external payable nonReentrant whenNotPaused {
require(msg.value >= auctionFee, "Insufficient auction fee");
require(_startingPrice > 0, "Starting price must be greater than 0");
require(_duration >= 3600 && _duration <= 604800, "Duration must be between 1 hour and 7 days");
IERC721 nft = IERC721(_nftContract);
require(nft.ownerOf(_tokenId) == msg.sender, "Not the owner");
require(nft.getApproved(_tokenId) == address(this), "Marketplace not approved");
uint256 auctionId = nextAuctionId++;
auctions[auctionId] = Auction({
seller: msg.sender,
nftContract: _nftContract,
tokenId: _tokenId,
startingPrice: _startingPrice,
highestBid: _startingPrice,
highestBidder: address(0),
endTime: block.timestamp + _duration,
isActive: true
});
nft.safeTransferFrom(msg.sender, address(this), _tokenId);
emit AuctionCreated(auctionId, msg.sender, _nftContract, _tokenId, _startingPrice, block.timestamp + _duration);
}
// Place bid
function placeBid(uint256 _auctionId) external payable nonReentrant {
Auction storage auction = auctions[_auctionId];
require(auction.isActive, "Auction not active");
require(block.timestamp < auction.endTime, "Auction ended");
require(msg.value > auction.highestBid, "Bid too low");
if (auction.highestBidder != address(0)) {
pendingWithdrawals[auction.highestBidder] += auction.highestBid;
}
auction.highestBid = msg.value;
auction.highestBidder = msg.sender;
emit BidPlaced(_auctionId, msg.sender, msg.value);
}
// End auction
function endAuction(uint256 _auctionId) external nonReentrant {
Auction storage auction = auctions[_auctionId];
require(auction.isActive, "Auction not active");
require(block.timestamp >= auction.endTime, "Auction not ended");
auction.isActive = false;
if (auction.highestBidder != address(0)) {
uint256 fee = (auction.highestBid * marketplaceFee) / 10000;
uint256 sellerProceeds = auction.highestBid - fee;
pendingWithdrawals[auction.seller] += sellerProceeds;
pendingWithdrawals[owner()] += fee;
IERC721(auction.nftContract).safeTransferFrom(address(this), auction.highestBidder, auction.tokenId);
emit AuctionEnded(_auctionId, auction.highestBidder, auction.highestBid);
} else {
IERC721(auction.nftContract).safeTransferFrom(address(this), auction.seller, auction.tokenId);
}
}
// Withdraw funds
function withdraw() external nonReentrant {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "No funds to withdraw");
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
// Admin functions
function setListingFee(uint256 _fee) external onlyOwner {
listingFee = _fee;
}
function setAuctionFee(uint256 _fee) external onlyOwner {
auctionFee = _fee;
}
function setMarketplaceFee(uint256 _fee) external onlyOwner {
require(_fee <= 1000, "Fee too high"); // Max 10%
marketplaceFee = _fee;
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
// Emergency functions
function emergencyWithdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
// IERC721Receiver implementation
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return this.onERC721Received.selector;
}
receive() external payable {}
}