DEV Community

Cover image for How to Build a Profitable NFT Marketplace with React, Solidity, and CometChat
Gospel Darlington
Gospel Darlington

Posted on • Updated on

How to Build a Profitable NFT Marketplace with React, Solidity, and CometChat

What you will be building, see demo on the Rinkeby test network and git repo here…

Introduction

Web3 is the internet's future, and we must all learn and embrace this technology. Its use-cases continue to emerge and have a positive impact on the world.

One magnificent application of web3 technology is the creation of non-fungible tokens (NFTs), which are a viable solution for digitalizing assets while retaining ownership rights.

This tutorial will teach you how to create a profitable and well-designed NFT marketplace with chat functionality.

Book your private classes with me if you need someone to help you learn web3 development faster.

With that said, let's get started...

Check out my YouTube channel for FREE web3 tutorials now.

Prerequisite

You will need the following tools installed to successfully crush this build:

  • Node
  • Ganache-Cli
  • Truffle
  • React
  • Infuria
  • Tailwind CSS
  • CometChat SDK
  • Metamask
  • Yarn

Installing Dependencies

NodeJs Installation
Check that NodeJs is already installed on your machine, and if not, install it from HERE. Run the code again on the terminal to ensure it is installed.

NodeJs Installed

Yarn, Ganache-cli and Truffle Installation
Run the commands below in your terminal to install these critical packages globally.

npm i -g yarn
npm i -g truffle
npm i -g ganache-cli
Enter fullscreen mode Exit fullscreen mode

To confirm installation, enter the following code into the terminal.

yarn --version && ganache-cli --version && truffle version
Enter fullscreen mode Exit fullscreen mode

Terminal Output

Cloning Web3 Starter Project
Clone the web 3.0 starter project using the commands below. This ensures that we're all on the same page and using the same software.

git clone https://github.com/Daltonic/timelessNFT
Enter fullscreen mode Exit fullscreen mode

Excellent, please replace the **package.json** file with the following:

After replacing the packages as directed, run **yarn install** on your terminal to load all of the packages with the specified versions.

Configuring CometChat SDK

Follow the steps below to configure the CometChat SDK; at the end, you must save these keys as an environment variable.

STEP 1:
Head to CometChat Dashboard and create an account.

Register a new CometChat account if you do not have one

STEP 2:
Log in to the CometChat dashboard, only after registering.

Log in to the CometChat Dashboard with your created account

STEP 3:
From the dashboard, add a new app called timelessNFT.

Create a new CometChat app - Step 1

Create a new CometChat app - Step 2

STEP 4:
Select the app you just created from the list.

Select your created app

STEP 5:
From the Quick Start copy the APP_ID, REGION, and AUTH_KEY, to your .env file. See the image and code snippet.

Copy the the APP_ID, REGION, and AUTH_KEY

Replace the REACT_COMET_CHAT placeholders keys with their appropriate values.

REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Enter fullscreen mode Exit fullscreen mode

Configuring Infuria App

STEP 1:
Head to Infuria, create an account.

Login to your infuria account

STEP 2:
From the dashboard create a new project.

Create a new project step 1

Create a new project step 2

STEP 3:
Copy the Rinkeby test network WebSocket endpoint URL to your .env file.

Rinkeby Testnet Keys

After that, enter your Metamask secret phrase and preferred account's private key. If you followed the instructions correctly, your environment variables should now look like this.

ENDPOINT_URL=***************************
DEPLOYER_KEY=**********************

REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Enter fullscreen mode Exit fullscreen mode

See the section below if you don't know how to access your private key.

Accessing Your Metamask Private Key

STEP 1:
Make sure Rinkeby is selected as the test network in your Metamask browser extension. Then, on the preferred account, click the vertical dotted line and choose account details. Please see the image below.

Step One

STEP 2:
Enter your password on the field provided and click the confirm button, this will enable you to access your account private key.

Step Two

STEP 3:
Click on "export private key" to see your private key. Make sure you never expose your keys on a public page such as Github. That is why we are appending it as an environment variable.

Step Three

STEP 4:
Copy your private key to your .env file. See the image and code snippet below:

Step Four

ENDPOINT_URL=***************************
SECRET_KEY=******************
DEPLOYER_KEY=**********************

REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Enter fullscreen mode Exit fullscreen mode

As for your SECRET_KEY, you are required to paste your Metamask secret phrase in the space provided in the environment file.

The TimlessNFT Smart Contract

Here is the complete smart contract code; Let’s go over all of the functions and variables one by one.

Code imports and contract information
In the code below, we informed the solidity compiler of the license identifier and compiler versions qualified to compile this code.

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "./ERC721.sol";
import "./ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TimelessNFT is ERC721Enumerable, Ownable {
  // codes goes in here...
}
Enter fullscreen mode Exit fullscreen mode

Also, this smart contract makes use of some openzepplin's ERC721 smart contracts. You have to make sure that you put them in the same directory as seen in the image below:

Essential Contracts

Visit this link and download these smart contracts as shown in the image above.

State variables declarations

using Strings for uint256;

mapping(string => uint8) existingURIs;
mapping(uint256 => address) public holderOf;

address public artist;
uint256 public royalityFee;
uint256 public supply = 0;
uint256 public totalTx = 0;
uint256 public cost = 0.01 ether;
Enter fullscreen mode Exit fullscreen mode

We specified that we’re using the string library for performing uint to string operations. Next, we also declared mappings for recording minted NFT artworks and also for knowing the current owner of a token.

Then we specified the other essential variables for capturing the artist account, the royalty fees, the current supply, total transactions that have been made on the platform, and the mint cost of an NFT.

Setting up events and structures

event Sale(
    uint256 id,
    address indexed owner,
    uint256 cost,
    string metadataURI,
    uint256 timestamp
);

struct TransactionStruct {
    uint256 id;
    address owner;
    uint256 cost;
    string title;
    string description;
    string metadataURI;
    uint256 timestamp;
}

TransactionStruct[] transactions;
TransactionStruct[] minted;
Enter fullscreen mode Exit fullscreen mode

In the preceding code, we have a sales event that emits data from any transaction that occurs on the smart contract, whether it is on minting or NFT transfer.

We designed a transaction structure to collect data about minted or transferred NFTs. Using the transaction structure we defined, we created two variables called transactions and minted.

Initializing the constructor

constructor(
    string memory _name,
    string memory _symbol,
    uint256 _royalityFee,
    address _artist
) ERC721(_name, _symbol) {
    royalityFee = _royalityFee;
    artist = _artist;
}
Enter fullscreen mode Exit fullscreen mode

The constructor takes in four parameters for initializing the smart contract. A token name, symbol, an artist account, and a royalty fee per transaction. The token name and symbol are then passed into the ERC721 smart contract during deployment.

The mint function algorithm

function payToMint(
    string memory title,
    string memory description,
    string memory metadataURI,
    uint256 salesPrice
) external payable {
    require(msg.value >= cost, "Ether too low for minting!");
    require(existingURIs[metadataURI] == 0, "This NFT is already minted!");
    require(msg.sender != owner(), "Sales not allowed!");


    uint256 royality = (msg.value * royalityFee) / 100;
    payTo(artist, royality);
    payTo(owner(), (msg.value - royality));

    supply++;

    minted.push(
        TransactionStruct(
            supply,
            msg.sender,
            salesPrice,
            title,
            description,
            metadataURI,
            block.timestamp
        )
    );

    emit Sale(
        supply,
        msg.sender,
        msg.value,
        metadataURI,
        block.timestamp
    );

    _safeMint(msg.sender, supply);
    existingURIs[metadataURI] = 1;
    holderOf[supply] = msg.sender;
}
Enter fullscreen mode Exit fullscreen mode

The above function is responsible for minting new tokens on the smart contract. The caller of this method must provide four parameters which include; an NFT title, description, metadata URI, and the selling price of the NFT after minting.

Validations are carried out to ensure that the NFT is minting and are done accordingly with payments made for each minting. Also, the validation ensures that each artwork is uniquely linked with a token, and no other token bears the same artwork. Lastly for the validation, we made sure that the caller of this method is not the deployer of the smart contract, this is to ensure we don’t mix up things too bad.

Next in the function is the payment sharing rule. The royalty percentage goes to the artist and the rest of the ethers goes to the owner.

Afterward, we recorded that NFT inside the minted array and emitted a sales event. Lastly, we minted the NFT while recording the caller’s address as the owner of the token.

The NFT transfer function algorithm

function payToBuy(uint256 id) external payable {
    require(msg.value >= minted[id - 1].cost, "Ether too low for purchase!");
    require(msg.sender != minted[id - 1].owner, "Operation Not Allowed!");

    uint256 royality = (msg.value * royalityFee) / 100;
    payTo(artist, royality);
    payTo(minted[id - 1].owner, (msg.value - royality));

    totalTx++;

    transactions.push(
        TransactionStruct(
            totalTx,
            msg.sender,
            msg.value,
            minted[id - 1].title,
            minted[id - 1].description,
            minted[id - 1].metadataURI,
            block.timestamp
        )
    );

    emit Sale(
        totalTx,
        msg.sender,
        msg.value,
        minted[id - 1].metadataURI,
        block.timestamp
    );

    minted[id - 1].owner = msg.sender;
}
Enter fullscreen mode Exit fullscreen mode

The above function takes an NFT id and makes a purchase of the NFT according to the set price by the minter (owner).

Necessary validations are done to obstruct owners from buying their NFTs and others from buying with zero ethers.

Next, a royalty fee is sent to the artist account and the current owner of the NFT gets to keep the rest.

Each token transfer is recorded in a transactions array to keep track of all transactions done on the platform.

Afterward, a sales event is again emitted for this purchase to enrich the logged data on the EVM.

Other Essential functions

// changes the price of an NFT
function changePrice(uint256 id, uint256 newPrice) external returns (bool) {
    require(newPrice > 0 ether, "Ether too low!");
    require(msg.sender == minted[id - 1].owner, "Operation Not Allowed!");

    minted[id - 1].cost = newPrice;
    return true;
}

// sends ethers to a specific account
function payTo(address to, uint256 amount) internal {
    (bool success, ) = payable(to).call{value: amount}("");
    require(success);
}

// returns all minted NFTs
function getAllNFTs() external view returns (TransactionStruct[] memory) {
    return minted;
}

// returns a specific NFT by token id
function getNFT(uint256 id) external view returns (TransactionStruct memory) {
    return minted[id - 1];
}

// returns all transactions
function getAllTransactions() external view returns (TransactionStruct[] memory) {
    return transactions;
}
Enter fullscreen mode Exit fullscreen mode

And there you have it for developing the smart contract, we will next dive into building the UI components with ReactJs.

Configuring the Deployment Script

One more thing to do with for the smart contract is to configure the deployment script.

On the project head to the migrations folder >> 2_deploy_contracts.js and update it with the code snippet below.

const TimelessNFT = artifacts.require('TimelessNFT')

module.exports = async (deployer) => {
  const accounts = await web3.eth.getAccounts()
  await deployer.deploy(
    TimelessNFT, 
    'Timeless NFTs', 
    'TNT', 10, accounts[1]
  )
}
Enter fullscreen mode Exit fullscreen mode

Superb, we just finished the smart contract for our application; now it's time to get started on the DApp interface. If you need a private tutor to help you learn smart contract development, book your classes with me.

Developing the Frontend

The front end is made up of numerous components and parts. All of the components, views, and peripherals will be created by us.

Header Component

The Header Component

This component was created with tailwind CSS and uses the pink Connect Wallet button to access the Metamask wallet. The codes below demonstrate the programming.

Hero Component

The Hero Component

This component is responsible for displaying the connected wallet and also for launching the modal used for creating a new NFT. Additionally, it is responsible for signing in or up users for one-on-one chats with a seller of an NFT. Here is the code responsible for these actions.

Artworks component

The Artworks Component

This component is responsible for rendering the list of NFTs minted on the platform using the beautifully crafted tailwind CSS cards. Each card has an NFT image, title, description, price, and owner. See the codes below for its implementation.

Transactions Component

The Transactions Component

This component is responsible for rendering all the transactions that took place in our smart contract. A transaction for example would be Alison purchasing an NFT from Duke. This purchase will be captured in this component as a transaction. See the snippet below.

Footer Component

The Footer Component

This component simply displays some beautiful links at the bottom of the page, it doesn’t do much when it comes to functionalities but complements the user interface. Its codes are written below.

Fantastic, that is it for the obvious components, let’s include the hidden components that are only invoked via a modal interface.

CreateNFT Component

The Create NFT Component

This component is saddled with the duty of minting new NFTs by supplying an image, title, price, and description. Once the Mint Now button is clicked, the image is uploaded to IPFS (Inter Planetary File System) and an image URL is returned.

The returned image URL along with the NFT data supplied in the form is sent to our smart contract for minting, immediately after the user authorizes the transaction with their Metamask wallet.

On completion of the transaction, the NFT is then listed among the artworks, and interested buyers can then purchase them and even change their prices. See the code below for details.

ShowNFT Component

Seller View
Buyer View

This component displays more information about a specific NFT, offering the owner a button to change the price, and the buyer a button to either purchase the NFT or chat with the seller. See the code below for more details.

UpdateNFT Component

The UpdateNFT Component

This component is tasked with changing the price of the NFT. This action can only be performed by the owner of the NFT. While this option is available, it will take some gas fee to effect these changes. Once an NFT exchanges a hand with another buyer, the new owner might decide to increase the price, and that’s why this option was made available. See the code snippet below.

ChatList Component

The ChatList Component

This component reveals the recent chats a user has made with a seller or buyer on the platform. The component also captures the last message that was sent in their conversation. A click on each conversation will lead to the chat interface. See the code below.

Chat Component

This component is responsible for engaging two users in a one-on-one chat. The image above shows a conversation between a buyer and a seller on the platform, from two different browsers. See the code below for its implementation.

Nice, now that we’ve included those fantastic components, let's finish it up with these last two.

Loading Component

The Loading Processing

This component simply displays the current activity and status when a transaction is in process. See the code below.

The App Component
This file bundles up the above component discussed in this work. This is just how a ReactJs architecture works. See the codes below.

Fantastic, we’ve just completed the integration of the various components, let’s seal it up with the other parts of this project.

Other Essential Files

This application utilizes a state management store, a CometChat SDK, and a contract service file. Let’s take a look at them one after the other.

The Store
This state management file uses the react-hooks-global-state npm package. It is simple, fast, and easier than Redux. All the global variables and functions used in this app were created in this store.

At the root of the project, go to the src directory and create a folder named store. Now, within this store folder, create a file called index.js and paste the codes below inside of it.

The CometChat Service
This file contains all the essential functions to communicate with the CometChat SDK. See the codes below.

The Contract Service File
This file contains all the functions and procedures responsible for interacting with the smart contract on the blockchain using the Web3 library. See the codes below.

Project Assets
Download this logo and include it inside the assets folder in your root directory. And with that, you’ve successfully included all that is needed to run this application.

Starting up the server

To proceed with this step, migrate the smart contract to the Web so you can interact with it. Run the following code on your terminal.

truffle migrate --network rinkeby
Enter fullscreen mode Exit fullscreen mode

The above code will ship your smart contract to the server using the infuria RPC.

You can also set up a local blockchain using the ganache-cli server we set up at the beginning of this tutorial. Simply run the code below to ship it to your local blockchain server if you prefer that way.

Open one terminal run **ganache-cli -d** and on a different terminal run **truffle migrate** or **truffle deploy**.

Note, if you are using ganache-cli as your EVM, you must also add the localhost server to Metamask, and import the private keys generated by ganache. See Starting Up the Development Environment for guidance.

If you need my help resolving issues on your project, consult me on this page.

Now, run yarn start to boot up your react application. And there you have it with this build on the NFT marketplace.

Watch my FREE web3 tutorials on YouTube now.

Conclusion

We’ve come to the finish line of this NFT build, I know you’ve gotten a ton of value building along with me.

Whatever level you are, if you want to grow faster in your web3 development skills, get into my private class.

Till next time, keep crushing it!

About the Author

Gospel Darlington is a full-stack blockchain developer with 6+ years of experience in the software development industry.

By combining Software Development, writing, and teaching, he demonstrates how to build decentralized applications on EVM-compatible blockchain networks.

His stacks include JavaScript, React, Vue, Angular, Node, React Native, NextJs, Solidity, and more.

For more information about him, kindly visit and follow his page on Twitter, Github, LinkedIn, or on his website.

Oldest comments (1)

Collapse
 
yongchanghe profile image
Yongchang He

Thank you for sharing!