Web-Based NFT Marketplace React, Solidity, JavaScript

👤 Sharing: AI
```javascript
// Frontend (React - src/App.js)

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers'; // Install with: npm install ethers
import NFTMarketplaceABI from './NFTMarketplace.json'; // Import ABI generated from Solidity contract compilation
import './App.css';

const NFT_MARKETPLACE_ADDRESS = 'YOUR_CONTRACT_ADDRESS'; // Replace with deployed contract address

function App() {
  const [nfts, setNfts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [account, setAccount] = useState(null);
  const [contract, setContract] = useState(null);
  const [itemIdToPurchase, setItemIdToPurchase] = useState(null);

  useEffect(() => {
    loadWeb3();
  }, []);

  const loadWeb3 = async () => {
    if (window.ethereum) {
      // Modern dapp browsers like Metamask
      try {
        await window.ethereum.enable(); // Request account access if needed

        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const signer = provider.getSigner();
        const address = await signer.getAddress();
        setAccount(address);

        const marketplaceContract = new ethers.Contract(
          NFT_MARKETPLACE_ADDRESS,
          NFTMarketplaceABI.abi, // Access the ABI definition from the imported JSON
          signer
        );
        setContract(marketplaceContract);

        await loadMarketplaceItems(marketplaceContract);
      } catch (error) {
        console.error("User denied account access or other error:", error);
        alert("Please connect to MetaMask to use this application.");
      }
    } else if (window.web3) {
      // Legacy dapp browsers...
      window.web3 = new Web3(window.web3.currentProvider);
    } else {
      // Non-dapp browsers...
      console.log('Non-Ethereum browser detected.  Consider trying MetaMask!');
      alert('Please install MetaMask to use this application.');
    }
  };


  const loadMarketplaceItems = async (marketplaceContract) => {
    setLoading(true);
    try {
      const itemCount = await marketplaceContract.itemCount();
      let items = [];
      for (let i = 1; i <= itemCount; i++) {
        const item = await marketplaceContract.items(i);
        items.push(item);
      }
      setNfts(items);
    } catch (error) {
      console.error("Error loading marketplace items:", error);
      alert("Failed to load marketplace items. Check console for details.");
    } finally {
      setLoading(false);
    }
  };



  const purchaseItem = async (itemId) => {
    try {
      await contract.purchaseItem(itemId, {
        value: ethers.utils.parseEther(nfts.find(nft => nft.itemId.toNumber() === itemId).price.toString()) // Correct way to access price and convert to Ether
      });
      await loadMarketplaceItems(contract); // Refresh the marketplace after purchase
    } catch (error) {
      console.error("Error purchasing item:", error);
      alert("Transaction failed. Check console for details. Ensure you have enough ETH.");
    }
  };


  if (loading) return (
    <main style={{ padding: "1rem 0" }}>
      <h2>Loading...</h2>
    </main>
  )


  return (
    <div className="container">
      <h1>NFT Marketplace</h1>
      <p>Connected Account: {account || "Not connected"}</p>

      <div className="nft-grid">
        {nfts.map(nft => (
          <div className="nft-card" key={nft.itemId.toNumber()}>
            <img src={`https://ipfs.io/ipfs/${nft.imageHash}`} alt={nft.name} />
            <h3>{nft.name}</h3>
            <p>{nft.description}</p>
            <p>Price: {ethers.utils.formatEther(nft.price)} ETH</p>
            {!nft.sold ? (
              <button onClick={() => purchaseItem(nft.itemId.toNumber())}>Buy</button>
            ) : (
              <p>Sold</p>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

export default App;
```

```javascript
// Solidity Contract (NFTMarketplace.sol)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract NFTMarketplace is ReentrancyGuard {
    using Counters for Counters.Counter;
    Counters.Counter private _itemIds;
    Counters.Counter private _itemsSold;

    address payable public owner;
    uint256 public listingPrice = 0.025 ether; // Example listing price

    struct MarketItem {
        uint itemId;
        address nftContract;
        uint256 tokenId;
        address payable seller;
        address payable owner;
        uint256 price;
        bool sold;
        string name; // Added NFT name
        string description; // Added NFT description
        string imageHash; // Added IPFS hash
    }

    mapping(uint256 => MarketItem) public items;
    uint256 public itemCount;


    event MarketItemCreated(
        uint indexed itemId,
        address indexed nftContract,
        uint256 indexed tokenId,
        address seller,
        address owner,
        uint256 price,
        bool sold,
        string name,
        string description,
        string imageHash
    );

    constructor() {
        owner = payable(msg.sender);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the owner can call this function.");
        _;
    }

    function updateListingPrice(uint256 _listingPrice) public onlyOwner {
        listingPrice = _listingPrice;
    }

    function getListingPrice() public view returns (uint256) {
        return listingPrice;
    }


    function createMarketItem(
        address nftContract,
        uint256 tokenId,
        uint256 price,
        string memory name,
        string memory description,
        string memory imageHash
    ) public payable nonReentrant {
        require(price > 0, "Price must be greater than 0");
        require(msg.value == listingPrice, "Price must be equal to listing price");

        _itemIds.increment();
        uint256 itemId = _itemIds.current();

        items[itemId] = MarketItem(
            itemId,
            nftContract,
            tokenId,
            payable(msg.sender),
            payable(address(this)), // Initially owned by the marketplace
            price,
            false,
            name,
            description,
            imageHash
        );
        itemCount++;

        IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);


        emit MarketItemCreated(
            itemId,
            nftContract,
            tokenId,
            msg.sender,
            address(this),
            price,
            false,
            name,
            description,
            imageHash
        );
    }

    function purchaseItem(uint256 itemId) public payable nonReentrant {
        MarketItem storage item = items[itemId];
        require(item.itemId != 0, "Item does not exist");
        require(msg.value == item.price, "Not enough ether to cover asking price");
        require(!item.sold, "Item already sold");

        item.seller.transfer(msg.value); // Send funds to the seller
        IERC721(item.nftContract).transferFrom(address(this), msg.sender, item.tokenId);

        item.owner = payable(msg.sender);
        item.sold = true;
        _itemsSold.increment();
    }

    function fetchMarketItems() public view returns (MarketItem[] memory) {
        uint totalItemCount = _itemIds.current();
        uint unsoldItemCount = totalItemCount - _itemsSold.current();
        uint currentIndex = 0;

        MarketItem[] memory items_ = new MarketItem[](unsoldItemCount);
        for (uint i = 0; i < totalItemCount; i++) {
            if (items[i + 1].owner == address(this)) {
                uint currentId = i + 1;
                MarketItem storage currentItem = items[currentId];
                items_[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items_;
    }

    function fetchMyNFTs() public view returns (MarketItem[] memory) {
        uint totalItemCount = _itemIds.current();
        uint itemCount = 0;
        for (uint i = 0; i < totalItemCount; i++) {
            if (items[i + 1].owner == msg.sender) {
                itemCount += 1;
            }
        }
        MarketItem[] memory items_ = new MarketItem[](itemCount);
        uint currentIndex = 0;
        for (uint i = 0; i < totalItemCount; i++) {
            if (items[i + 1].owner == msg.sender) {
                uint currentId = i + 1;
                MarketItem storage currentItem = items[currentId];
                items_[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items_;
    }

    function fetchItemsCreated() public view returns (MarketItem[] memory) {
        uint totalItemCount = _itemIds.current();
        uint itemCount = 0;
        for (uint i = 0; i < totalItemCount; i++) {
            if (items[i + 1].seller == msg.sender) {
                itemCount += 1;
            }
        }
        MarketItem[] memory items_ = new MarketItem[](itemCount);
        uint currentIndex = 0;
        for (uint i = 0; i < totalItemCount; i++) {
            if (items[i + 1].seller == msg.sender) {
                uint currentId = i + 1;
                MarketItem storage currentItem = items[currentId];
                items_[currentIndex] = currentItem;
                currentIndex += 1;
            }
        }
        return items_;
    }
}
```

```javascript
// Backend (simple example - not essential for core functionality)
// Node.js with Express (optional)
// Install: npm install express cors

// index.js
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3001;

app.use(cors());

app.get('/api/nfts', (req, res) => {
  // In a real application, this would fetch NFT data from a database or external API
  const mockNFTData = [
    { id: 1, name: 'Awesome NFT 1', price: 1.5 },
    { id: 2, name: 'Cool NFT 2', price: 2.0 },
  ];
  res.json(mockNFTData);
});

app.listen(port, () => {
  console.log(`Backend listening at http://localhost:${port}`);
});
```

**Explanation:**

**1.  Solidity Smart Contract (NFTMarketplace.sol):**

*   **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version.

*   **`import "@openzeppelin/contracts/token/ERC721/ERC721.sol";`**: Imports the OpenZeppelin ERC721 contract, providing standard NFT functionality.  You'll need to install this: `npm install @openzeppelin/contracts`.
*   **`import "@openzeppelin/contracts/utils/Counters.sol";`**: Imports the `Counters` utility from OpenZeppelin, used to generate unique item IDs. Install with: `npm install @openzeppelin/contracts`.
*   **`import "@openzeppelin/contracts/security/ReentrancyGuard.sol";`**: Imports `ReentrancyGuard` to prevent reentrancy attacks.

*   **`contract NFTMarketplace is ReentrancyGuard { ... }`**: Defines the `NFTMarketplace` contract, inheriting from `ReentrancyGuard`.

*   **`using Counters for Counters.Counter;`**:  Allows you to use `Counters` functions on `Counters.Counter` variables.
*   **`Counters.Counter private _itemIds;`**: Keeps track of the next available item ID.
*   **`Counters.Counter private _itemsSold;`**: Keeps track of the number of items sold.
*   **`address payable public owner;`**: Stores the address of the contract owner (who deployed the contract).
*   **`uint256 public listingPrice = 0.025 ether;`**:  Sets the price required to list an NFT on the marketplace (payable by the seller).
*   **`struct MarketItem { ... }`**: Defines a struct to represent an item listed on the marketplace. It includes details like:
    *   `itemId`: Unique identifier for the item.
    *   `nftContract`: Address of the ERC721 contract for the NFT.
    *   `tokenId`:  ID of the specific NFT within the ERC721 contract.
    *   `seller`: Address of the person who listed the NFT.
    *   `owner`: Address of the current owner of the NFT (initially the marketplace).
    *   `price`: Price of the NFT in Wei.
    *   `sold`: Boolean indicating whether the NFT has been sold.
    *   `name`: The name of the NFT.
    *   `description`:  Description of the NFT.
    *   `imageHash`: IPFS hash of the NFT's image.
*   **`mapping(uint256 => MarketItem) public items;`**:  A mapping that stores `MarketItem` structs, indexed by their `itemId`.

*   **`event MarketItemCreated(...)`**:  An event emitted when a new item is listed on the marketplace.  Events are useful for tracking activity on the blockchain.

*   **`constructor() { ... }`**:  The constructor sets the `owner` to the address that deployed the contract.

*   **`modifier onlyOwner() { ... }`**:  A modifier that restricts access to certain functions to only the contract owner.

*   **`updateListingPrice(uint256 _listingPrice)`**:  Allows the owner to change the listing price.

*   **`getListingPrice()`**:  Returns the current listing price.

*   **`createMarketItem(...)`**:  Allows a user to list an NFT on the marketplace. It:
    *   Requires the `price` to be greater than 0 and `msg.value` (the amount of Ether sent with the transaction) to be equal to the `listingPrice`.
    *   Increments the `_itemIds` counter to get a new `itemId`.
    *   Creates a `MarketItem` struct and stores it in the `items` mapping.
    *   Transfers the NFT from the seller to the marketplace contract using `IERC721(nftContract).transferFrom(...)`. This requires the seller to have approved the marketplace contract to transfer the NFT on their behalf.
    *   Emits the `MarketItemCreated` event.

*   **`purchaseItem(uint256 itemId)`**:  Allows a user to purchase an NFT. It:
    *   Requires the `itemId` to exist, `msg.value` to be equal to the item's `price`, and the item to not be already sold.
    *   Transfers the Ether from the buyer to the seller.
    *   Transfers the NFT from the marketplace contract to the buyer.
    *   Updates the `owner` and `sold` fields of the `MarketItem`.
    *   Increments `_itemsSold`.

*   **`fetchMarketItems()`, `fetchMyNFTs()`, `fetchItemsCreated()`**: Functions to retrieve different sets of marketplace items.

**2.  React Frontend (App.js):**

*   **`import React, { useState, useEffect } from 'react';`**: Imports React hooks for managing state and side effects.
*   **`import { ethers } from 'ethers';`**:  Imports the `ethers` library for interacting with the Ethereum blockchain.  Install with: `npm install ethers`.
*   **`import NFTMarketplaceABI from './NFTMarketplace.json';`**: Imports the ABI (Application Binary Interface) of the `NFTMarketplace` contract.  This file is generated when you compile your Solidity contract.
*   **`const NFT_MARKETPLACE_ADDRESS = 'YOUR_CONTRACT_ADDRESS';`**:  Replace this with the address of your deployed `NFTMarketplace` contract.

*   **`useState(...)`**:  Uses the `useState` hook to manage the following state variables:
    *   `nfts`: An array of NFT objects retrieved from the contract.
    *   `loading`: A boolean indicating whether the data is being loaded.
    *   `account`: The user's connected Ethereum account address.
    *   `contract`: The `ethers.Contract` instance for interacting with the `NFTMarketplace` contract.
    *   `itemIdToPurchase`: The ID of the item selected for purchase.

*   **`useEffect(() => { loadWeb3(); }, []);`**:  Uses the `useEffect` hook to call `loadWeb3()` when the component mounts.  The empty dependency array `[]` ensures that it only runs once.

*   **`loadWeb3()`**:  Connects to the user's Ethereum provider (e.g., MetaMask) and initializes the `ethers` provider and signer.
    *   It checks for `window.ethereum` (modern dapp browsers).
    *   It requests account access from MetaMask using `await window.ethereum.enable()`.
    *   It creates an `ethers.providers.Web3Provider` instance.
    *   It gets the signer (the user's account) and their address.
    *   It creates an `ethers.Contract` instance, connecting to the `NFTMarketplace` contract using its address, ABI, and the signer.
    *   It calls `loadMarketplaceItems()` to fetch the initial list of NFTs.

*   **`loadMarketplaceItems(marketplaceContract)`**:  Fetches the NFT items from the contract.
    *   Calls the `itemCount()` function on the contract to get the total number of items.
    *   Iterates through the item IDs (from 1 to `itemCount`) and calls the `items()` function on the contract for each ID to retrieve the `MarketItem` struct.
    *   Stores the `MarketItem` structs in the `nfts` state variable.
    *   Handles potential errors.
*   **`purchaseItem(itemId)`**: Allows the user to purchase an NFT by calling the `purchaseItem` function on the contract.
    *   Uses `ethers.utils.parseEther()` to convert the NFT's `price` (which is in Ether) to Wei (the smallest unit of Ether).
    *   Passes the `itemId` to the `purchaseItem` function on the contract.
    *   Sets the `value` option in the transaction to the price of the NFT. This is how the Ether is sent along with the transaction.
    *   Calls `loadMarketplaceItems()` to refresh the list of NFTs after the purchase.
    *   Handles potential errors.

*   **JSX (the `return` statement)**:  Renders the UI.
    *   Displays a loading message while the data is being fetched.
    *   Displays the connected account address.
    *   Maps over the `nfts` array and renders an NFT card for each item.  The card displays the NFT's image, name, description, and price.
    *   Includes a "Buy" button if the NFT is not sold, and a "Sold" message if it is.
    *   The `onClick` handler for the "Buy" button calls the `purchaseItem()` function.

**3. Backend (Optional - simple example):**

*   This is a very basic example using Node.js and Express. It's not strictly necessary for the core functionality, but a backend server is often used in real-world applications for things like:
    *   Storing NFT metadata (images, descriptions, etc.) in a database.
    *   Caching data to improve performance.
    *   Integrating with other services.

*   **`npm install express cors`**:  Installs the Express and CORS packages.
*   **`app.get('/api/nfts', (req, res) => { ... });`**:  Defines a route that returns a list of NFTs.
*   **`cors()`**:  Enables Cross-Origin Resource Sharing (CORS) to allow the frontend to make requests to the backend.

**Important Considerations and Steps:**

1.  **Install Dependencies:**
    ```bash
    npm install ethers @openzeppelin/contracts
    npm install react-scripts  // In your React project
    ```

2.  **Compile the Solidity Contract:**
    *   Use the Solidity compiler (e.g., Remix IDE) to compile `NFTMarketplace.sol`.
    *   This will generate the ABI (Application Binary Interface) and bytecode.
    *   Save the ABI to a JSON file (e.g., `NFTMarketplace.json`) in your React project's `src` directory.

3.  **Deploy the Contract:**
    *   Deploy the compiled contract to a test network (e.g., Ropsten, Goerli, Sepolia) or a local development network (e.g., Ganache) using Remix, Truffle, Hardhat, or another deployment tool.
    *   **Important:** Get the deployed contract address. You will need this in your React app.

4.  **Update `NFT_MARKETPLACE_ADDRESS`:**
    *   In `src/App.js`, replace `"YOUR_CONTRACT_ADDRESS"` with the actual address of your deployed contract.

5.  **Set Up MetaMask:**
    *   Install the MetaMask browser extension.
    *   Configure MetaMask to connect to the same network you deployed the contract to.
    *   Make sure your MetaMask account has enough Ether to pay for transactions.

6.  **Run the React App:**
    ```bash
    npm start  // In your React project directory
    ```

7.  **Approve Marketplace for Transfers**
    When listing NFTs, the user will need to approve the marketplace to transfer the NFTs on their behalf.  This is usually a separate transaction on the ERC721 contract. You can add a function like this to your React component:

    ```javascript
    const approveNFTTransfer = async (nftContractAddress, tokenId) => {
        try {
            const provider = new ethers.providers.Web3Provider(window.ethereum);
            const signer = provider.getSigner();
            const nftContract = new ethers.Contract(nftContractAddress, [
                "function approve(address to, uint256 tokenId) public",
            ], signer);

            const transaction = await nftContract.approve(NFT_MARKETPLACE_ADDRESS, tokenId);
            await transaction.wait();  // Wait for transaction to be mined
            alert("NFT transfer approved!");
        } catch (error) {
            console.error("Error approving NFT transfer:", error);
            alert("Error approving NFT transfer. Check console for details.");
        }
    };
    ```

8.  **IPFS for NFT Metadata:**
    In a real application, you'll want to store your NFT metadata (images, names, descriptions) on a decentralized storage system like IPFS (InterPlanetary File System).

    *   Upload your NFT image to IPFS using a service like Pinata or NFT.Storage.  You'll get a hash (CID) for the image.
    *   When creating the `MarketItem`, store the IPFS hash in the `imageHash` field.
    *   In your React app, use the IPFS hash to display the image. The frontend code already fetches the image from `https://ipfs.io/ipfs/${nft.imageHash}`.

**Important Considerations and Improvements:**

*   **Error Handling:**  Add more robust error handling to both the frontend and backend.
*   **User Interface:**  Improve the UI with better styling, responsiveness, and user experience.
*   **NFT Creation:** Integrate an NFT creation flow (minting) so users can create and list their own NFTs.  You will need to create an ERC721 contract and integrate it with the marketplace.
*   **Testing:**  Write unit tests for your Solidity contract.
*   **Security:**  Thoroughly review your contract for security vulnerabilities.  Consider using a security audit.
*   **Gas Optimization:**  Optimize your Solidity contract for gas efficiency.
*   **Events:** Use events extensively in your Solidity contract to provide a history of actions.
*   **Backend:**  Create a more robust backend with a database to store NFT metadata, user profiles, and other data.
*   **Lazy Minting:**  Implement lazy minting to avoid upfront gas costs for NFT creators.
*   **Royalties:** Implement royalty payments to the original creators when NFTs are resold.
*   **Off-Chain Metadata:**  Store NFT metadata off-chain (e.g., on IPFS) to save on gas costs.  The NFT contract only stores the URI of the metadata.

This comprehensive example provides a solid foundation for building a web-based NFT marketplace using React, Solidity, and JavaScript.  Remember to replace placeholders with your actual contract address and IPFS hashes.  Good luck!
👁️ Viewed: 8

Comments