Skip to content

NFT Marketplace Contract

Overview

The NFT Marketplace contract (NFTMarketplace) implements a fully functional NFT trading marketplace that supports NFT listing, purchasing, and delisting functions. The contract uses a direct sale model where sellers can set fixed prices and buyers can purchase directly.

Contract Implementation

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 Implementation of NFT Marketplace Contract
 */
contract NFTMarketplace is ReentrancyGuard, Pausable, Ownable {
    using SafeMath for uint256;

    // Listing information structure
    struct Listing {
        address seller;          // Seller address
        address nftContract;     // NFT contract address
        uint256 tokenId;        // NFT ID
        uint256 price;          // Price
        address payToken;       // Payment token address (address(0) for ETH)
        bool active;            // Whether active
    }

    // Offer information structure
    struct Offer {
        address buyer;          // Buyer address
        uint256 price;          // Offer price
        uint256 expiresAt;     // Expiration time
        bool active;           // Whether active
    }

    // State variables
    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 definitions
    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
     */
    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 Create listing
     */
    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 Update listing price
     */
    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 Cancel listing
     */
    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 Buy 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 Create offer
     */
    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 Cancel offer
     */
    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;
        
        // Return 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 Accept offer
     */
    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 Execute sale
     */
    function _executeSale(
        uint256 listingId,
        address buyer,
        uint256 price
    ) internal {
        Listing storage listing = listings[listingId];
        
        // Calculate platform fee
        uint256 fee = price.mul(platformFee).div(10000);
        uint256 sellerProceeds = price.sub(fee);

        // Transfer payment
        if (listing.payToken == address(0)) {
            // Pay platform fee
            if (fee > 0) {
                (bool feeSuccess, ) = feeRecipient.call{value: fee}("");
                require(feeSuccess, "Fee transfer failed");
            }
            // Pay seller
            (bool success, ) = listing.seller.call{value: sellerProceeds}("");
            require(success, "Seller payment failed");
        } else {
            IERC20 payToken = IERC20(listing.payToken);
            // Pay platform fee
            if (fee > 0) {
                require(
                    payToken.transferFrom(buyer, feeRecipient, fee),
                    "Fee transfer failed"
                );
            }
            // Pay seller
            require(
                payToken.transferFrom(buyer, listing.seller, sellerProceeds),
                "Seller payment failed"
            );
        }

        // Transfer NFT
        IERC721(listing.nftContract).safeTransferFrom(
            listing.seller,
            buyer,
            listing.tokenId
        );

        // Update state
        listing.active = false;

        emit ListingSold(listingId, buyer, price);
    }

    /**
     * @dev Add payment token
     */
    function addPayToken(address tokenAddress) external onlyOwner {
        require(tokenAddress != address(0), "Invalid token address");
        approvedPayTokens[tokenAddress] = true;
    }

    /**
     * @dev Remove payment token
     */
    function removePayToken(address tokenAddress) external onlyOwner {
        approvedPayTokens[tokenAddress] = false;
    }

    /**
     * @dev Update platform fee
     */
    function updatePlatformFee(uint256 _platformFee) external onlyOwner {
        require(_platformFee <= 1000, "Platform fee must be <= 10%");
        platformFee = _platformFee;
    }

    /**
     * @dev Update fee recipient
     */
    function updateFeeRecipient(address _feeRecipient) external onlyOwner {
        require(_feeRecipient != address(0), "Invalid fee recipient");
        feeRecipient = _feeRecipient;
    }

    /**
     * @dev Emergency pause
     */
    function pause() external onlyOwner {
        _pause();
    }

    /**
     * @dev Resume operation
     */
    function unpause() external onlyOwner {
        _unpause();
    }

    /**
     * @dev Receive NFT callback
     */
    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}

Functionality Description

1. Market Management

  • List NFT: Support fixed price listing
  • Update Price: Allow sellers to update prices
  • Cancel Listing: Support sellers to cancel listings
  • Platform Fee: Support collecting transaction fees

2. Trading Mechanism

  • Direct Purchase: Purchase NFTs at fixed prices
  • Offer System: Support buyers to make offers
  • Multi-Token Payment: Support ETH and ERC20 tokens
  • Automatic Settlement: Complete token transfer and fund allocation

3. Offer Management

  • Create Offer: Buyers can submit offers
  • Cancel Offer: Buyers can cancel offers
  • Accept Offer: Sellers can accept offers
  • Expiration Mechanism: Offers automatically expire

4. Security Mechanism

  • Reentrancy Protection: Prevent reentrancy attacks
  • Pause Function: Contract can be paused in case of emergencies
  • Permission Control: Only the owner can manage the contract
  • Parameter Validation: Strict input parameter checks

Usage Example

1. List NFT

javascript
const price = ethers.utils.parseEther("1");    // Price 1 ETH
const payToken = ethers.constants.AddressZero; // Use ETH for payment

await nftMarketplace.createListing(
    nft.address,      // NFT contract address
    tokenId,          // NFT ID
    price,
    payToken
);

2. Buy NFT

javascript
await nftMarketplace.buyNFT(listingId, {
    value: ethers.utils.parseEther("1")
});

3. Create Offer

javascript
const price = ethers.utils.parseEther("0.8");    // Offer price 0.8 ETH
const duration = 86400;                          // Offer duration 1 day

await nftMarketplace.createOffer(
    listingId,
    price,
    duration,
    {
        value: price
    }
);

4. Accept Offer

javascript
await nftMarketplace.acceptOffer(listingId, buyerAddress);

Best Practices

1. Market Setup

  • Reasonable platform fee ratio
  • Strict token whitelist
  • Comprehensive price validation

2. Security Considerations

  • Regularly check contract status
  • Monitor abnormal transactions
  • Prepare emergency plans

3. Trading Management

  • Handle expired offers promptly
  • Monitor transaction completion
  • Maintain price reasonability

4. Fee Calculation

  • Platform fee calculation method:
solidity
// Listing fee
listingFee = price * listingFeeRate / 10000

// Trading fee
tradingFee = price * tradingFeeRate / 10000

// Royalty
royalty = price * royaltyRate / 10000
  • Metadata handling method:
  • Off-chain storage (IPFS/Arweave)
  • Indexing on chain
  • Caching mechanism
  • Data validation
  • Update mechanism

6. Best Practices

  • Suggestions for improving transaction success rate:
  • Reasonable pricing strategy
  • Complete work information
  • High-quality display materials
  • Active community interaction
  • Good buyer experience

7. Collection Management

  • Suggestions for managing NFT collections:
  • Create series standards
  • Set access permissions
  • Manage metadata
  • Track transaction data
  • Maintain community relationships

8. Error Handling

  • Common errors and solutions:
  • "Not owner": Check NFT ownership
  • "Not approved": Confirm authorization status
  • "Invalid price": Correct pricing settings
  • "Insufficient funds": Ensure sufficient funds
  • "Already listed": Check listing status

9. Upgrade and Maintenance

  • Upgrade strategies:
  • Use proxy contracts
  • Modular design
  • Version control
  • Smooth migration
  • Backward compatibility

10. Integration and Interoperability

  • Integration methods:
  • Standard interface compatibility
  • Cross-protocol interoperability
  • Aggregator support
  • Liquidity sharing
  • Data sharing

11. Data and Analysis

  • Data tracking methods:
  • Event listening
  • Price tracking
  • Transaction volume analysis
  • User behavior analysis
  • Market trend reports

12. Data Application

  • Data application:
  • Pricing reference
  • Market prediction
  • Risk assessment
  • User profiling
  • Operational decision-making

Released under the MIT License by Vogeb.