How to Create an NFT Marketplace from Scratch

Raji moshood - Feb 18 - - Dev Community

Introduction

Non-Fungible Tokens (NFTs) have revolutionized digital ownership, enabling artists, developers, and collectors to trade unique assets on the blockchain. In this guide, we will build an NFT marketplace from scratch, covering minting, storing NFTs on IPFS, and deploying smart contracts on Ethereum.

What You'll Learn

✔ How to mint and list NFTs
✔ How to store NFT metadata on IPFS
✔ How to deploy a smart contract for trading NFTs
✔ How to integrate Web3.js or Ethers.js for front-end interaction

  1. Setting Up the Development Environment

Prerequisites

Before we start, install the following:

Node.js (v16 or later)

Hardhat (Ethereum development framework)

MetaMask (for interacting with Ethereum testnets)

Pinata or NFT.Storage (for IPFS storage)

Step 1: Initialize Hardhat Project

mkdir nft-marketplace && cd nft-marketplace
npm init -y
npm install --save-dev hardhat
npx hardhat
Enter fullscreen mode Exit fullscreen mode

Select “Create a basic sample project” when prompted.

  1. Writing the NFT Smart Contract

Step 1: Install OpenZeppelin Contracts

npm install @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Step 2: Create an ERC-721 Contract

Inside contracts/, create NFTMarketplace.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTMarketplace is ERC721URIStorage, Ownable {
    uint256 private _tokenIdCounter;
    uint256 public listingFee = 0.01 ether;

    struct NFT {
        uint256 tokenId;
        address payable owner;
        uint256 price;
        bool listed;
    }

    mapping(uint256 => NFT) public nfts;

    constructor() ERC721("NFT Marketplace", "NFTM") {}

    function mintNFT(string memory tokenURI, uint256 price) public payable {
        require(msg.value >= listingFee, "Insufficient listing fee");

        _tokenIdCounter++;
        uint256 tokenId = _tokenIdCounter;

        _mint(msg.sender, tokenId);
        _setTokenURI(tokenId, tokenURI);

        nfts[tokenId] = NFT(tokenId, payable(msg.sender), price, true);
    }

    function buyNFT(uint256 tokenId) public payable {
        NFT memory nft = nfts[tokenId];
        require(nft.listed, "NFT not listed for sale");
        require(msg.value >= nft.price, "Insufficient funds");

        payable(nft.owner).transfer(msg.value);
        _transfer(nft.owner, msg.sender, tokenId);
        nfts[tokenId].owner = payable(msg.sender);
        nfts[tokenId].listed = false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Compile the Contract

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode
  1. Deploying the Smart Contract

Modify scripts/deploy.js:

const hre = require("hardhat");

async function main() {
  const NFTMarketplace = await hre.ethers.getContractFactory("NFTMarketplace");
  const marketplace = await NFTMarketplace.deploy();
  await marketplace.deployed();
  console.log("NFT Marketplace deployed at:", marketplace.address);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
Enter fullscreen mode Exit fullscreen mode

Run the deployment script:

npx hardhat run scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode
  1. Storing NFT Metadata on IPFS

To store NFT images and metadata, use Pinata or NFT.Storage.

Step 1: Upload Image to IPFS

Go to Pinata and create an account.

Upload an image and copy the CID (Content Identifier).

Step 2: Create Metadata JSON

Save the following JSON as metadata.json:

{
  "name": "Crypto Art",
  "description": "A unique digital artwork",
  "image": "ipfs://YOUR_IMAGE_CID"
}
Enter fullscreen mode Exit fullscreen mode

Upload this metadata.json to Pinata and copy its CID.

  1. Connecting the dApp to MetaMask with Web3.js & Ethers.js

Step 1: Install Dependencies

npm install web3 ethers
Enter fullscreen mode Exit fullscreen mode

Step 2: Connect React Frontend

In App.js:

import { useState } from "react";
import { ethers } from "ethers";

const contractAddress = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
const contractABI = [
  {
    "inputs": [
      { "internalType": "string", "name": "tokenURI", "type": "string" },
      { "internalType": "uint256", "name": "price", "type": "uint256" }
    ],
    "name": "mintNFT",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  }
];

function App() {
  const [account, setAccount] = useState("");

  async function connectWallet() {
    if (window.ethereum) {
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const accounts = await provider.send("eth_requestAccounts", []);
      setAccount(accounts[0]);
    } else {
      alert("MetaMask not found");
    }
  }

  return (
    <div>
      <button onClick={connectWallet}>Connect Wallet</button>
      <p>Connected Account: {account}</p>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  1. Minting an NFT from the Frontend

Modify App.js to include minting functionality:

async function mintNFT() {
  if (!account) return alert("Connect wallet first");

  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  const contract = new ethers.Contract(contractAddress, contractABI, signer);

  const metadataURI = "ipfs://YOUR_METADATA_CID";
  const price = ethers.utils.parseEther("0.1");

  const tx = await contract.mintNFT(metadataURI, price, { value: ethers.utils.parseEther("0.01") });
  await tx.wait();
  alert("NFT Minted Successfully!");
}
Enter fullscreen mode Exit fullscreen mode

Add this button to the return statement in App.js:

<button onClick={mintNFT}>Mint NFT</button>
Enter fullscreen mode Exit fullscreen mode
  1. Buying an NFT from the Marketplace

Modify App.js to include purchasing functionality:

async function buyNFT(tokenId, price) {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  const contract = new ethers.Contract(contractAddress, contractABI, signer);

  const tx = await contract.buyNFT(tokenId, { value: price });
  await tx.wait();
  alert("NFT Purchased Successfully!");
}
Enter fullscreen mode Exit fullscreen mode

Add this button to the UI for each NFT listing:

<button onClick={() => buyNFT(1, ethers.utils.parseEther("0.1"))}>Buy NFT</button>
Enter fullscreen mode Exit fullscreen mode
  1. Deploying to Testnet

Modify hardhat.config.js:

module.exports = {
  solidity: "0.8.19",
  networks: {
    goerli: {
      url: "https://eth-goerli.alchemyapi.io/v2/YOUR_ALCHEMY_API_KEY",
      accounts: ["YOUR_PRIVATE_KEY"]
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Deploy the contract:

npx hardhat run scripts/deploy.js --network goerli
Enter fullscreen mode Exit fullscreen mode

Conclusion

We successfully built an NFT marketplace with:
✔ Smart contracts for minting & trading NFTs
✔ IPFS storage for NFT metadata
✔ React frontend with Web3.js & Ethers.js
✔ MetaMask wallet integration

This marketplace can be extended with auction functionality, royalties, and multi-chain support.

NFTs #Ethereum #Solidity #IPFS #Web3 #React #MetaMask

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .