Breaking down NFT Marketpalce and Auction System

Breaking down NFT Marketpalce and Auction System

In this post, we'll explore two Solidity smart contracts in detail: an NFT Marketplace and an NFT Auction System.Beyond the functionality, we’ll discuss the importance of access modifiers, visibility keywords, and key design decisions made while writing these contracts.

You can find the contract on my Github : Contract

These two contracts form the foundation for decentralized platforms where users can create, buy, sell, and auction NFTs (Non-Fungible Tokens). Let’s go through the key elements, exploring not just what the code does, but why and how it’s structured to ensure security and efficiency.

Contract 1 : NFT Marketplace

The NFT Marketplace contract allows users to create, list, and sell NFTs, offering a decentralized platform for trading digital assets. It utilizes the OpenZeppelin library, which is a widely-used set of modular, secure smart contract standards that simplify the development of Ethereum-based applications.

Key Features of the NFT Marketplace Contract

  1. NFT Creation (Minting): Users can create unique NFTs by specifying metadata (stored as a URI).

  2. Listing for Sale: Once minted, the NFT can be listed for sale on the marketplace.

  3. Purchasing and Reselling: NFTs can be bought by others and later resold at a new price.

Detailed Breakdown of the Contract

Imports and Libraries: To keep the contract secure and efficient, we use several OpenZeppelin contracts:

  • ERC721URIStorage: Extends the ERC721 standard for NFT creation, adding the ability to store metadata using URIs.

  • Counters.sol: A utility for managing unique token IDs, ensuring each NFT minted has a unique identifier.

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

State Variables: The contract keeps track of:

  • _tokenIds: A counter that ensures each NFT receives a unique token ID when minted.

  • _itemsSold: Tracks how many NFTs have been sold in the marketplace.

  • listingPrice: This is the fee required to list an NFT for sale on the platform, set initially at 0.0025 ETH.

  • owner: The marketplace’s owner, who has the authority to change the listing price.

using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
Counters.Counter private _itemsSold;
uint256 listingPrice = 0.0025 ether;
address payable owner;

Modifier: onlyOwner We define a custom modifier onlyOwner that restricts certain functions to the contract’s owner. This enforces security by preventing unauthorized access to sensitive functions like setting the listing price.

modifier onlyOwner() {
    require(msg.sender == owner, "Only owner can call this function");
    _;
}

Struct: MarketItem: The contract uses a struct to represent each NFT listed for sale. It stores:

  • tokenId: The ID of the NFT.

  • owner: The current owner of the NFT.

  • seller: The person who originally listed the NFT for sale.

  • price: The price at which the NFT is listed.

  • sold: A boolean value indicating if the NFT has already been sold.

struct MarketItem {
    uint256 tokenId;
    address payable owner;
    address payable seller;
    uint256 price;
    bool sold;
}

Functions:

  1. Minting an NFT: createToken This function allows users to mint (create) an NFT by providing metadata in the form of a URI and a desired sale price. The function:

    • Increments the token ID counter.

    • Mints the NFT with the specified tokenURI.

    • Calls the createMarketItem function to list the NFT in the marketplace.

function createToken(string memory tokenURI, uint256 price) public payable returns (uint256) {
    _tokenIds.increment();
    uint256 newTokenId = _tokenIds.current();
    _mint(msg.sender, newTokenId);
    _setTokenURI(newTokenId, tokenURI);
    createMarketItem(newTokenId, price);
    return newTokenId;
}
  1. Listing the NFT for Sale: createMarketItem After minting, the NFT can be listed for sale on the marketplace. This function:

    • Checks that the sale price is greater than 0.

    • Ensures the sender pays the required listing price.

    • Transfers the NFT to the marketplace contract for listing.

function createMarketItem(uint256 tokenId, uint256 price) private {
    require(price > 0, "Price must be at least 1 wei");
    require(msg.value == listingPrice, "Price must equal listing price");

    idMarketItem[tokenId] = MarketItem(
        tokenId, 
        payable(msg.sender), 
        payable(address(this)), 
        price, 
        false
    );

    _transfer(msg.sender, address(this), tokenId);
}
  1. Purchasing the NFT: createMarketSale Buyers can purchase NFTs listed for sale. This function:

    • Checks that the correct price is paid.

    • Transfers the NFT from the marketplace to the buyer.

    • Sends the payment to the seller.

    • Marks the NFT as sold and updates the total number of items sold.

function createMarketSale(uint256 tokenId) public payable {
    uint256 price = idMarketItem[tokenId].price;
    require(msg.value == price, "Please submit the asking price");

    idMarketItem[tokenId].seller.transfer(msg.value);
    _transfer(address(this), msg.sender, tokenId);
    idMarketItem[tokenId].owner = payable(msg.sender);
    idMarketItem[tokenId].sold = true;
    _itemsSold.increment();
}
  1. Fetching Listed and Owned NFTs:

    • fetchMarketItems: Fetches all NFTs that are currently listed for sale and unsold.

    • fetchMyNFTs: Returns all NFTs owned by the caller.

    • fetchItemsListed: Lists all NFTs that the caller has listed for sale.

These functions provide a convenient way for users to manage their NFTs within the marketplace.

Contract 2: NFT Auction System

The NFT Auction System uses a Dutch auction model where the price starts high and decreases over time until a buyer makes a purchase.

Key Features of the Auction Contract

  1. Dutch Auction Mechanism: The price decreases at a fixed rate over time, incentivizing buyers to act before the auction ends.

  2. Self-Destruct After Sale: After the auction ends and the NFT is sold, the contract self-destructs, transferring any remaining funds back to the seller.

Contract Breakdown

State Variables:

  • nft: The NFT contract being auctioned.

  • nftId: The ID of the specific NFT being auctioned.

  • seller: The address of the person auctioning the NFT.

  • startingPrice and discountRate: These define how the price decreases over time.

  • startAt and expiresAt: Timestamps marking the start and end of the auction period.

contract DutchAuction {
    uint256 public constant DURATION = 7 days;
    IERC721 public immutable nft;
    uint256 public immutable nftId;
    address payable public immutable seller;
    uint256 public immutable startingPrice;
    uint256 public immutable discountRate;
    uint256 public startAt;
    uint256 public expiresAt;
}

Constructor: The constructor initializes the auction by setting the seller, starting price, discount rate, NFT contract, and token ID.

constructor(
    uint256 _startingPrice, 
    uint256 _discountRate, 
    address _nft, 
    uint256 _nftId
) {
    seller = payable(msg.sender);
    startingPrice = _startingPrice;
    discountRate = _discountRate;
    startAt = block.timestamp;
    expiresAt = block.timestamp + DURATION;
    nft = IERC721(_nft);
    nftId = _nftId;
}

Function: getPrice This function calculates the current price based on the elapsed time and the discount rate.

function getPrice() public view returns (uint256) {
    uint256 timeElapsed = block.timestamp - startAt;
    uint256 discount = discountRate * timeElapsed;
    return startingPrice - discount;
}

Function: buy To purchase the NFT, a buyer must pay the current price. The NFT is transferred to the buyer, and any excess payment is refunded. After a successful purchase, the contract self-destructs, sending any remaining balance to the seller.

function buy() external payable {
    require(block.timestamp < expiresAt, "Auction ended");
    uint256 price = getPrice();
    require(msg.value >= price, "Insufficient funds sent");

    nft.transferFrom(seller, msg.sender, nftId);
    uint256 refund = msg.value - price;
    if (refund > 0) {
        payable(msg.sender).transfer(refund);
    }
    selfdestruct(seller);
}

The use of selfdestruct(seller) is a deliberate choice to optimize gas costs. After the auction ends, the contract is no longer needed, so it’s destroyed, sending the remaining funds back to the seller.


Conclusion

These two smart contracts form the backbone of decentralized NFT platforms. The NFT Marketplace allows users to create, list, and trade NFTs, while the NFT Auction System provides an innovative way to auction NFTs using a decreasing price model. Both contracts ensure transparency, security, and efficiency through the use of Solidity and Ethereum standards.

By understanding these smart contracts, you can create your own decentralized NFT marketplace and auction system, opening up possibilities for artists, collectors, and investors alike in the growing world of blockchain and NFTs.