Implementing NFT Marketplace on Website
NFT marketplace is a platform for issuing, listing, buying, and auctioning NFTs. A complete marketplace includes smart contracts on blockchain, frontend to interact with them, and backend to index data.
Marketplace Components
Frontend (Next.js + Wagmi)
│
├── Smart Contracts (Solidity)
│ ├── NFT Collection (ERC-721/ERC-1155)
│ ├── Marketplace Contract (listing, buy, auction)
│ └── Royalty (EIP-2981)
│
├── Indexer (The Graph / custom)
│ └── Reads blockchain events → PostgreSQL
│
└── Backend API
├── NFT Metadata (IPFS/Arweave)
└── Analytics, search, filters
Smart Contract: Marketplace
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFTMarketplace is ReentrancyGuard, Ownable {
uint256 public platformFee = 250; // 2.5% in basis points
uint256 public constant FEE_DENOMINATOR = 10000;
struct Listing {
address seller;
address nftContract;
uint256 tokenId;
uint256 price;
bool active;
}
mapping(bytes32 => Listing) public listings;
mapping(address => uint256) public proceeds;
event NFTListed(
bytes32 indexed listingId,
address indexed seller,
address indexed nftContract,
uint256 tokenId,
uint256 price
);
event NFTSold(
bytes32 indexed listingId,
address indexed buyer,
uint256 price
);
function listNFT(
address nftContract,
uint256 tokenId,
uint256 price
) external returns (bytes32 listingId) {
require(price > 0, "Price must be > 0");
IERC721 nft = IERC721(nftContract);
require(nft.ownerOf(tokenId) == msg.sender, "Not owner");
require(
nft.isApprovedForAll(msg.sender, address(this)) ||
nft.getApproved(tokenId) == address(this),
"Marketplace not approved"
);
listingId = keccak256(abi.encodePacked(nftContract, tokenId, msg.sender, block.timestamp));
listings[listingId] = Listing({
seller: msg.sender,
nftContract: nftContract,
tokenId: tokenId,
price: price,
active: true
});
emit NFTListed(listingId, msg.sender, nftContract, tokenId, price);
}
function buyNFT(bytes32 listingId) external payable nonReentrant {
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.value >= listing.price, "Insufficient payment");
listing.active = false;
// Platform fee
uint256 fee = (listing.price * platformFee) / FEE_DENOMINATOR;
uint256 sellerAmount = listing.price - fee;
// Royalty (EIP-2981)
(address royaltyReceiver, uint256 royaltyAmount) = _getRoyalty(
listing.nftContract, listing.tokenId, listing.price
);
if (royaltyAmount > 0 && royaltyAmount < sellerAmount) {
sellerAmount -= royaltyAmount;
proceeds[royaltyReceiver] += royaltyAmount;
}
proceeds[listing.seller] += sellerAmount;
proceeds[owner()] += fee;
// Transfer NFT to buyer
IERC721(listing.nftContract).safeTransferFrom(
listing.seller, msg.sender, listing.tokenId
);
// Return overpayment
if (msg.value > listing.price) {
payable(msg.sender).transfer(msg.value - listing.price);
}
emit NFTSold(listingId, msg.sender, listing.price);
}
function withdrawProceeds() external nonReentrant {
uint256 amount = proceeds[msg.sender];
require(amount > 0, "No proceeds");
proceeds[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
Frontend: Listing and Purchase
import { useWriteContract, useReadContract, useAccount } from 'wagmi';
import { parseEther } from 'viem';
function BuyNFT({ listingId, price }) {
const { address } = useAccount();
const { writeContractAsync, isPending } = useWriteContract();
const { data: listing } = useReadContract({
address: MARKETPLACE_ADDRESS,
abi: marketplaceAbi,
functionName: 'listings',
args: [listingId]
});
const handleBuy = async () => {
await writeContractAsync({
address: MARKETPLACE_ADDRESS,
abi: marketplaceAbi,
functionName: 'buyNFT',
args: [listingId],
value: price
});
};
return (
<button onClick={handleBuy} disabled={isPending || !address}>
{isPending ? 'Processing...' : `Buy for ${formatEther(price)} ETH`}
</button>
);
}
NFT Metadata (IPFS)
import { NFTStorage } from 'nft.storage';
const client = new NFTStorage({ token: process.env.NFT_STORAGE_KEY });
async function uploadNFTMetadata(
file: File,
name: string,
description: string,
attributes: Array<{ trait_type: string; value: string }>
): Promise<string> {
const metadata = await client.store({
name,
description,
image: file,
attributes
});
// Returns IPFS URI: ipfs://bafyrei...
return metadata.url;
}
Indexing via The Graph
// subgraph.yaml
dataSources:
- kind: ethereum
name: NFTMarketplace
source:
address: "0xYourMarketplaceContract"
abi: NFTMarketplace
mapping:
eventHandlers:
- event: NFTListed(indexed bytes32,indexed address,indexed address,uint256,uint256)
handler: handleNFTListed
- event: NFTSold(indexed bytes32,indexed address,uint256)
handler: handleNFTSold
// mapping.ts (AssemblyScript)
export function handleNFTListed(event: NFTListed): void {
let listing = new Listing(event.params.listingId.toHex());
listing.seller = event.params.seller;
listing.nftContract = event.params.nftContract;
listing.tokenId = event.params.tokenId;
listing.price = event.params.price;
listing.active = true;
listing.createdAt = event.block.timestamp;
listing.save();
}
Development Timeline
- Smart contracts (listing + sale) + tests — 2–3 weeks
- Frontend with Wagmi + listing/purchase/display — 2–3 weeks
- Indexer (The Graph) + search/filters — 1–2 weeks
- Complete marketplace with auction and royalties — 2–3 months







