NFT marketplaces have evolved significantly in 2025. From simple trading platforms to sophisticated decentralized exchanges with advanced features like lazy minting, gasless transactions, and cross-chain compatibility. Let's build a production-ready NFT marketplace using modern standards.
Understanding NFT Standards
ERC-721: Non-Fungible Tokens
Each token is unique and indivisible:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.05 ether;
uint256 public constant MAX_MINT_PER_TX = 10;
bool public mintingActive = false;
string public baseTokenURI;
event Minted(address indexed minter, uint256 tokenId, string tokenURI);
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(uint256 quantity) external payable {
require(mintingActive, "Minting not active");
require(quantity > 0 && quantity <= MAX_MINT_PER_TX, "Invalid quantity");
require(_tokenIdCounter.current() + quantity <= MAX_SUPPLY, "Exceeds max supply");
require(msg.value >= MINT_PRICE * quantity, "Insufficient payment");
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
string memory tokenURI = string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId)));
_setTokenURI(tokenId, tokenURI);
emit Minted(msg.sender, tokenId, tokenURI);
}
// Refund excess payment
if (msg.value > MINT_PRICE * quantity) {
payable(msg.sender).transfer(msg.value - MINT_PRICE * quantity);
}
}
function toggleMinting() external onlyOwner {
mintingActive = !mintingActive;
}
function setBaseURI(string memory newBaseURI) external onlyOwner {
baseTokenURI = newBaseURI;
}
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
payable(owner()).transfer(balance);
}
// Required overrides
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function totalSupply() public view returns (uint256) {
return _tokenIdCounter.current();
}
}ERC-1155: Multi-Token Standard
More efficient for collections and gaming:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
contract GameItems is ERC1155, Ownable, ERC1155Supply, ERC1155Burnable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant SWORD = 2;
uint256 public constant SHIELD = 3;
uint256 public constant THORS_HAMMER = 4;
string private _baseURI;
mapping(uint256 => string) private _tokenURIs;
event Minted(address indexed minter, uint256 id, uint256 amount);
constructor() ERC1155("") Ownable(msg.sender) {
// Initial mint
_mint(msg.sender, GOLD, 10**18, "");
_mint(msg.sender, SWORD, 10**3, "");
}
function mint(address account, uint256 id, uint256 amount, bytes memory data)
public
onlyOwner
{
_mint(account, id, amount, data);
emit Minted(account, id, amount);
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
public
onlyOwner
{
_mintBatch(to, ids, amounts, data);
}
function setBaseURI(string memory newBaseURI) public onlyOwner {
_baseURI = newBaseURI;
}
function setTokenURI(uint256 id, string memory newURI) public onlyOwner {
_tokenURIs[id] = newURI;
}
function uri(uint256 id) public view override returns (string memory) {
if (bytes(_tokenURIs[id]).length > 0) {
return _tokenURIs[id];
}
return string(abi.encodePacked(_baseURI, Strings.toString(id), ".json"));
}
// Required overrides
function _update(address from, address to, uint256[] memory ids, uint256[] memory values)
internal
override(ERC1155, ERC1155Supply)
{
super._update(from, to, ids, values);
}
}Marketplace Smart Contract
Complete marketplace with listing, bidding, and auction features:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFTMarketplace is ReentrancyGuard, Ownable {
// Market fees
uint256 public constant MARKET_FEE = 250; // 2.5%
uint256 public constant FEE_DENOMINATOR = 10000;
// Listing types
enum ListingType { Direct, Auction }
// ERC-721 Listing
struct Listing721 {
uint256 listingId;
address nftContract;
uint256 tokenId;
address seller;
uint256 price;
ListingType listingType;
uint256 deadline;
bool active;
}
// ERC-1155 Listing
struct Listing1155 {
uint256 listingId;
address nftContract;
uint256 tokenId;
address seller;
uint256 price;
uint256 amount;
ListingType listingType;
uint256 deadline;
bool active;
}
// Bid
struct Bid {
address bidder;
uint256 amount;
uint256 timestamp;
}
// State variables
uint256 private _listingIdCounter;
mapping(uint256 => Listing721) public listings721;
mapping(uint256 => Listing1155) public listings1155;
mapping(uint256 => Bid[]) public bids;
// Events
event Listed721(uint256 indexed listingId, address indexed seller, uint256 price);
event Listed1155(uint256 indexed listingId, address indexed seller, uint256 amount, uint256 price);
event Sold721(uint256 indexed listingId, address indexed seller, address indexed buyer, uint256 price);
event Sold1155(uint256 indexed listingId, address indexed seller, address indexed buyer, uint256 amount, uint256 price);
event BidPlaced(uint256 indexed listingId, address indexed bidder, uint256 amount);
event BidAccepted(uint256 indexed listingId, address indexed seller, address indexed bidder, uint256 amount);
event Cancelled(uint256 indexed listingId, address indexed seller);
constructor() Ownable(msg.sender) {}
// List ERC-721 for sale
function list721(
address nftContract,
uint256 tokenId,
uint256 price,
ListingType listingType,
uint256 deadline
) external nonReentrant {
require(price > 0, "Price must be greater than 0");
require(listingType == ListingType.Direct || deadline > block.timestamp, "Invalid deadline");
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
uint256 listingId = _listingIdCounter++;
listings721[listingId] = Listing721({
listingId: listingId,
nftContract: nftContract,
tokenId: tokenId,
seller: msg.sender,
price: price,
listingType: listingType,
deadline: deadline,
active: true
});
emit Listed721(listingId, msg.sender, price);
}
// List ERC-1155 for sale
function list1155(
address nftContract,
uint256 tokenId,
uint256 amount,
uint256 price,
ListingType listingType,
uint256 deadline
) external nonReentrant {
require(price > 0, "Price must be greater than 0");
require(amount > 0, "Amount must be greater than 0");
require(listingType == ListingType.Direct || deadline > block.timestamp, "Invalid deadline");
IERC1155(nftContract).safeTransferFrom(msg.sender, address(this), tokenId, amount, "");
uint256 listingId = _listingIdCounter++;
listings1155[listingId] = Listing1155({
listingId: listingId,
nftContract: nftContract,
tokenId: tokenId,
seller: msg.sender,
price: price,
amount: amount,
listingType: listingType,
deadline: deadline,
active: true
});
emit Listed1155(listingId, msg.sender, amount, price);
}
// Buy ERC-721 directly
function buy721(uint256 listingId) external payable nonReentrant {
Listing721 storage listing = listings721[listingId];
require(listing.active, "Listing not active");
require(listing.listingType == ListingType.Direct, "Not a direct listing");
require(msg.value >= listing.price, "Insufficient payment");
listing.active = false;
uint256 marketFee = (listing.price * MARKET_FEE) / FEE_DENOMINATOR;
uint256 sellerAmount = listing.price - marketFee;
payable(listing.seller).transfer(sellerAmount);
IERC721(listing.nftContract).transferFrom(address(this), msg.sender, listing.tokenId);
emit Sold721(listingId, listing.seller, msg.sender, listing.price);
// Refund excess payment
if (msg.value > listing.price) {
payable(msg.sender).transfer(msg.value - listing.price);
}
}
// Buy ERC-1155 tokens
function buy1155(uint256 listingId, uint256 amount) external payable nonReentrant {
Listing1155 storage listing = listings1155[listingId];
require(listing.active, "Listing not active");
require(listing.listingType == ListingType.Direct, "Not a direct listing");
require(amount <= listing.amount, "Insufficient amount");
require(msg.value >= listing.price * amount, "Insufficient payment");
uint256 totalPrice = listing.price * amount;
uint256 marketFee = (totalPrice * MARKET_FEE) / FEE_DENOMINATOR;
uint256 sellerAmount = totalPrice - marketFee;
payable(listing.seller).transfer(sellerAmount);
IERC1155(listing.nftContract).safeTransferFrom(address(this), msg.sender, listing.tokenId, amount, "");
listing.amount -= amount;
if (listing.amount == 0) {
listing.active = false;
}
emit Sold1155(listingId, listing.seller, msg.sender, amount, totalPrice);
// Refund excess payment
if (msg.value > totalPrice) {
payable(msg.sender).transfer(msg.value - totalPrice);
}
}
// Place bid on auction
function placeBid(uint256 listingId) external payable nonReentrant {
Listing721 storage listing = listings721[listingId];
require(listing.active, "Listing not active");
require(listing.listingType == ListingType.Auction, "Not an auction");
require(block.timestamp < listing.deadline, "Auction ended");
require(msg.value > listing.price, "Bid too low");
// Refund previous highest bidder
if (bids[listingId].length > 0) {
Bid storage lastBid = bids[listingId][bids[listingId].length - 1];
payable(lastBid.bidder).transfer(lastBid.amount);
}
bids[listingId].push(Bid({
bidder: msg.sender,
amount: msg.value,
timestamp: block.timestamp
}));
listing.price = msg.value;
emit BidPlaced(listingId, msg.sender, msg.value);
}
// Accept bid (only seller)
function acceptBid(uint256 listingId) external nonReentrant {
Listing721 storage listing = listings721[listingId];
require(listing.active, "Listing not active");
require(listing.seller == msg.sender, "Not the seller");
require(bids[listingId].length > 0, "No bids");
Bid memory highestBid = bids[listingId][bids[listingId].length - 1];
listing.active = false;
uint256 marketFee = (highestBid.amount * MARKET_FEE) / FEE_DENOMINATOR;
uint256 sellerAmount = highestBid.amount - marketFee;
payable(msg.sender).transfer(sellerAmount);
IERC721(listing.nftContract).transferFrom(address(this), highestBid.bidder, listing.tokenId);
emit BidAccepted(listingId, msg.sender, highestBid.bidder, highestBid.amount);
}
// Cancel listing
function cancelListing(uint256 listingId) external nonReentrant {
if (listings721[listingId].active) {
Listing721 storage listing = listings721[listingId];
require(listing.seller == msg.sender, "Not the seller");
require(bids[listingId].length == 0, "Has active bids");
listing.active = false;
IERC721(listing.nftContract).transferFrom(address(this), msg.sender, listing.tokenId);
emit Cancelled(listingId, msg.sender);
} else if (listings1155[listingId].active) {
Listing1155 storage listing = listings1155[listingId];
require(listing.seller == msg.sender, "Not the seller");
listing.active = false;
IERC1155(listing.nftContract).safeTransferFrom(address(this), msg.sender, listing.tokenId, listing.amount, "");
emit Cancelled(listingId, msg.sender);
}
}
// Emergency functions
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
payable(owner()).transfer(balance);
}
function setMarketFee(uint256 newFee) external onlyOwner {
require(newFee <= 1000, "Fee too high"); // Max 10%
MARKET_FEE = newFee;
}
}Lazy Minting (Gas-Free)
Allow creators to mint without paying gas:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
contract LazyMinting is ERC721, EIP712 {
using ECDSA for bytes32;
bytes32 private constant LAZY_MINT_TYPEHASH =
keccak256("LazyMint(address minter,uint256 tokenId,string tokenURI,uint256 price)");
address public verifier;
mapping(uint256 => bool) public mintedTokens;
constructor() ERC721("LazyNFT", "LNFT") EIP712("LazyNFT", "1") {
verifier = msg.sender;
}
function lazyMint(
uint256 tokenId,
string memory tokenURI,
uint256 price,
bytes memory signature
) external payable {
require(msg.value >= price, "Insufficient payment");
require(!mintedTokens[tokenId], "Token already minted");
// Verify signature
bytes32 structHash = keccak256(abi.encode(
LAZY_MINT_TYPEHASH,
msg.sender,
tokenId,
keccak256(bytes(tokenURI)),
price
));
bytes32 digest = _hashTypedDataV4(structHash);
address signer = digest.recover(signature);
require(signer == verifier, "Invalid signature");
mintedTokens[tokenId] = true;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, tokenURI);
payable(verifier).transfer(msg.value);
}
}Frontend Integration
React components for the marketplace:
// src/components/NFTMarketplace.tsx
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import { useWallet } from '../hooks/useWallet';
import { NFTMarketplace__factory } from '../contracts/types';
interface NFT {
tokenId: string;
price: string;
seller: string;
tokenURI: string;
image: string;
name: string;
description: string;
}
export function NFTMarketplace() {
const { wallet, provider, connect } = useWallet();
const [nfts, setNfts] = useState<NFT[]>([]);
const [loading, setLoading] = useState(false);
const MARKETPLACE_ADDRESS = '0x...';
const RPC_URL = 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID';
useEffect(() => {
if (provider) {
loadListings();
}
}, [provider]);
const loadListings = async () => {
if (!provider) return;
setLoading(true);
try {
const marketplace = NFTMarketplace__factory.connect(
MARKETPLACE_ADDRESS,
provider
);
// Get all listings (you'd want to paginate this)
const listingCount = await marketplace.listingIdCounter();
const listings: NFT[] = [];
for (let i = 0; i < Number(listingCount); i++) {
const listing = await marketplace.listings721(i);
if (!listing.active) continue;
const nftContract = new ethers.Contract(
listing.nftContract,
['function tokenURI(uint256) view returns (string)'],
provider
);
const tokenURI = await nftContract.tokenURI(listing.tokenId);
const metadata = await fetchMetadata(tokenURI);
listings.push({
tokenId: listing.tokenId.toString(),
price: ethers.formatEther(listing.price),
seller: listing.seller,
tokenURI,
image: metadata.image,
name: metadata.name,
description: metadata.description,
});
}
setNfts(listings);
} catch (error) {
console.error('Error loading listings:', error);
} finally {
setLoading(false);
}
};
const fetchMetadata = async (uri: string) => {
const response = await fetch(uri);
return await response.json();
};
const buyNFT = async (listingId: string, price: string) => {
if (!provider || !wallet.address) return;
const signer = await provider.getSigner();
const marketplace = NFTMarketplace__factory.connect(
MARKETPLACE_ADDRESS,
signer
);
try {
const priceWei = ethers.parseEther(price);
const tx = await marketplace.buy721(listingId, { value: priceWei });
await tx.wait();
alert('NFT purchased successfully!');
loadListings();
} catch (error) {
console.error('Error purchasing NFT:', error);
}
};
const listNFT = async (
nftContract: string,
tokenId: string,
price: string
) => {
if (!provider || !wallet.address) return;
const signer = await provider.getSigner();
const marketplace = NFTMarketplace__factory.connect(
MARKETPLACE_ADDRESS,
signer
);
try {
const priceWei = ethers.parseEther(price);
// Approve marketplace first
const nftContract_ = new ethers.Contract(
nftContract,
['function approve(address to, uint256 tokenId)'],
signer
);
const approveTx = await nftContract_.approve(MARKETPLACE_ADDRESS, tokenId);
await approveTx.wait();
// List NFT
const listTx = await marketplace.list721(
nftContract,
tokenId,
priceWei,
0, // Direct listing
0 // No deadline
);
await listTx.wait();
alert('NFT listed successfully!');
loadListings();
} catch (error) {
console.error('Error listing NFT:', error);
}
};
if (!wallet.isConnected) {
return <button onClick={connect}>Connect Wallet</button>;
}
if (loading) {
return <div>Loading NFTs...</div>;
}
return (
<div className="nft-marketplace">
<h1>NFT Marketplace</h1>
<div className="nft-grid">
{nfts.map((nft) => (
<div key={nft.tokenId} className="nft-card">
<img src={nft.image} alt={nft.name} />
<h3>{nft.name}</h3>
<p>{nft.description}</p>
<p className="price">{nft.price} ETH</p>
<p className="seller">Seller: {nft.seller.slice(0, 6)}...{nft.seller.slice(-4)}</p>
{wallet.address.toLowerCase() !== nft.seller.toLowerCase() && (
<button onClick={() => buyNFT(nft.tokenId, nft.price)}>
Buy Now
</button>
)}
</div>
))}
</div>
<div className="list-nft">
<h2>List Your NFT</h2>
<input
type="text"
placeholder="NFT Contract Address"
id="nft-contract"
/>
<input
type="number"
placeholder="Token ID"
id="token-id"
/>
<input
type="number"
placeholder="Price (ETH)"
id="price"
/>
<button
onClick={() => {
const contract = (document.getElementById('nft-contract') as HTMLInputElement).value;
const tokenId = (document.getElementById('token-id') as HTMLInputElement).value;
const price = (document.getElementById('price') as HTMLInputElement).value;
listNFT(contract, tokenId, price);
}}
>
List NFT
</button>
</div>
</div>
);
}IPFS Integration
Store NFT metadata on IPFS:
// src/utils/ipfs.ts
import { create } from 'ipfs-http-client';
const ipfs = create({
url: 'https://ipfs.infura.io:5001/api/v0',
headers: {
authorization: 'Basic ' + Buffer.from('PROJECT-ID:PROJECT-SECRET').toString('base64'),
},
});
export async function uploadToIPFS(data: any) {
const result = await ipfs.add(JSON.stringify(data));
return `ipfs://${result.path}`;
}
export async function uploadImageToIPFS(file: File) {
const result = await ipfs.add(file);
return `ipfs://${result.path}`;
}
// Example usage
export async function createNFTMetadata(
name: string,
description: string,
image: File
) {
// Upload image
const imageCID = await uploadImageToIPFS(image);
// Create metadata
const metadata = {
name,
description,
image: `ipfs://${imageCID}`,
attributes: [
{ trait_type: 'Rarity', value: 'Rare' },
{ trait_type: 'Generation', value: '1' },
],
};
// Upload metadata
const metadataCID = await uploadToIPFS(metadata);
return metadataCID;
}Royalty Enforcement
Implement on-chain royalties:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@rarible/royalties/contracts/impl/RoyaltiesV2Impl.sol";
import "@rarible/royalties/contracts/LibPart.sol";
contract RoyaltyNFT is ERC721, RoyaltiesV2Impl {
mapping(uint256 => LibPart.Part[]) private _royalties;
function mint(
address to,
uint256 tokenId,
uint256 royaltyPercentage
) external {
_safeMint(to, tokenId);
// Set royalty (max 10%)
require(royaltyPercentage <= 1000, "Royalty too high");
_royalties[tokenId] = [LibPart.Part({
account: msg.sender,
value: royaltyPercentage
})];
}
function getRoyalties(uint256 tokenId)
external
view
override
returns (LibPart.Part[] memory)
{
return _royalties[tokenId];
}
function _transfer(
address from,
address to,
uint256 tokenId
) internal override {
super._transfer(from, to, tokenId);
// Distribute royalties
if (from != address(0)) {
LibPart.Part[] memory royalties = _royalties[tokenId];
for (uint256 i = 0; i < royalties.length; i++) {
// Distribute royalty to creator
payable(royalties[i].account).transfer(
(msg.value * royalties[i].value) / 10000
);
}
}
}
}Advanced Features
Batch Transfers
function batchTransfer(address[] calldata recipients, uint256[] calldata tokenIds)
external
{
require(recipients.length == tokenIds.length, "Length mismatch");
require(recipients.length <= 200, "Batch too large");
for (uint256 i = 0; i < recipients.length; i++) {
_transfer(msg.sender, recipients[i], tokenIds[i]);
}
}Automated Market Making (AMM)
contract NFTAMM {
struct Pool {
address nftContract;
uint256[] tokenIds;
uint256 ethLiquidity;
uint256 lpTokens;
}
mapping(address => Pool) public pools;
function createPool(address nftContract, uint256[] calldata tokenIds)
external
payable
{
// Transfer NFTs to pool
for (uint256 i = 0; i < tokenIds.length; i++) {
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenIds[i]);
}
pools[nftContract] = Pool({
nftContract: nftContract,
tokenIds: tokenIds,
ethLiquidity: msg.value,
lpTokens: msg.value
});
}
function swap(address nftContract, uint256 tokenId)
external
payable
{
Pool storage pool = pools[nftContract];
// Calculate price based on constant product formula
uint256 price = (pool.ethLiquidity * pool.tokenIds.length) / pool.tokenIds.length;
require(msg.value >= price, "Insufficient payment");
pool.ethLiquidity += msg.value;
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
}
}Testing
Comprehensive test suite:
// NFTMarketplace.test.ts
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { NFTMarketplace, MyNFT } from '../typechain';
describe('NFTMarketplace', () => {
let marketplace: NFTMarketplace;
let nft: MyNFT;
let owner: any;
let buyer: any;
beforeEach(async () => {
[owner, buyer] = await ethers.getSigners();
const NFT = await ethers.getContractFactory('MyNFT');
nft = await NFT.deploy();
await nft.waitForDeployment();
const Marketplace = await ethers.getContractFactory('NFTMarketplace');
marketplace = await Marketplace.deploy();
await marketplace.waitForDeployment();
});
it('should list NFT for sale', async () => {
await nft.mint(1);
await nft.approve(await marketplace.getAddress(), 0);
await marketplace.list721(
await nft.getAddress(),
0,
ethers.parseEther('1'),
0,
0
);
const listing = await marketplace.listings721(0);
expect(listing.price).to.equal(ethers.parseEther('1'));
expect(listing.active).to.be.true;
});
it('should buy NFT', async () => {
await nft.mint(1);
await nft.approve(await marketplace.getAddress(), 0);
await marketplace.list721(
await nft.getAddress(),
0,
ethers.parseEther('1'),
0,
0
);
await marketplace.connect(buyer).buy721(0, {
value: ethers.parseEther('1')
});
expect(await nft.ownerOf(0)).to.equal(buyer.address);
});
it('should revert if payment is insufficient', async () => {
await nft.mint(1);
await nft.approve(await marketplace.getAddress(), 0);
await marketplace.list721(
await nft.getAddress(),
0,
ethers.parseEther('1'),
0,
0
);
await expect(
marketplace.connect(buyer).buy721(0, {
value: ethers.parseEther('0.5')
})
).to.be.revertedWith('Insufficient payment');
});
});Deployment Checklist
Before deploying your marketplace:
- 1Smart Contract Audit
- [ ] Reentrancy protection - [ ] Access control validation - [ ] Integer overflow checks - [ ] Emergency stop mechanism
- 1Testing
- [ ] Unit tests for all functions - [ ] Integration tests - [ ] Gas optimization tests - [ ] Front-end testing
- 1Infrastructure
- [ ] IPFS setup for metadata - [ ] The Graph for indexing - [ ] Relayer for gasless transactions - [ ] Monitoring and analytics
- 1Security
- [ ] Rate limiting - [ ] Input validation - [ ] Oracle integration for pricing - [ ] Insurance fund
Conclusion
Building a production-ready NFT marketplace in 2025 requires:
- 1Proper Standards: Use ERC-721 and ERC-1155 appropriately
- 2Security First: Implement all security best practices
- 3User Experience: Gas optimization, lazy minting, batch operations
- 4Scalability: IPFS, The Graph, layer-2 solutions
- 5Flexibility: Support for auctions, bids, and advanced trading
Start with a simple marketplace and gradually add features based on user feedback and market demands. The NFT space is rapidly evolving, so stay updated with the latest standards and best practices.