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
- 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
Select “Create a basic sample project” when prompted.
- Writing the NFT Smart Contract
Step 1: Install OpenZeppelin Contracts
npm install @openzeppelin/contracts
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;
}
}
Step 3: Compile the Contract
npx hardhat compile
- 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);
});
Run the deployment script:
npx hardhat run scripts/deploy.js --network localhost
- 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"
}
Upload this metadata.json to Pinata and copy its CID.
- Connecting the dApp to MetaMask with Web3.js & Ethers.js
Step 1: Install Dependencies
npm install web3 ethers
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;
- 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!");
}
Add this button to the return statement in App.js:
<button onClick={mintNFT}>Mint NFT</button>
- 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!");
}
Add this button to the UI for each NFT listing:
<button onClick={() => buyNFT(1, ethers.utils.parseEther("0.1"))}>Buy NFT</button>
- 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"]
}
}
};
Deploy the contract:
npx hardhat run scripts/deploy.js --network goerli
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.