Skip to content
All essays
CraftMarch 20, 202520 min

NFT Marketplace Development: Building with ERC-721 and ERC-1155

Build a production-ready NFT marketplace with ERC-721 and ERC-1155 standards. Learn smart contracts, frontend integration, and best practices for 2025.

Ü
Ümit Uz
Mobile & Full Stack Developer

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:

solidity
// 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:

solidity
// 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:

solidity
// 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:

solidity
// 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:

typescript
// 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:

typescript
// 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:

solidity
// 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

solidity
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)

solidity
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:

typescript
// 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:

  1. 1Smart Contract Audit

- [ ] Reentrancy protection - [ ] Access control validation - [ ] Integer overflow checks - [ ] Emergency stop mechanism

  1. 1Testing

- [ ] Unit tests for all functions - [ ] Integration tests - [ ] Gas optimization tests - [ ] Front-end testing

  1. 1Infrastructure

- [ ] IPFS setup for metadata - [ ] The Graph for indexing - [ ] Relayer for gasless transactions - [ ] Monitoring and analytics

  1. 1Security

- [ ] Rate limiting - [ ] Input validation - [ ] Oracle integration for pricing - [ ] Insurance fund

Conclusion

Building a production-ready NFT marketplace in 2025 requires:

  1. 1Proper Standards: Use ERC-721 and ERC-1155 appropriately
  2. 2Security First: Implement all security best practices
  3. 3User Experience: Gas optimization, lazy minting, batch operations
  4. 4Scalability: IPFS, The Graph, layer-2 solutions
  5. 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.

Related essays

Next essay
WebAssembly Performance Optimization: Memory & Speed Tuning