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