DEV Community

Cover image for How to Build an Awesome Web3 Crowd-Funding Platform with React, Solidity, and CometChat
Gospel Darlington
Gospel Darlington

Posted on

How to Build an Awesome Web3 Crowd-Funding Platform with React, Solidity, and CometChat

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

Add Project

Introduction

A valuable skill in Web3 development is all you need to secure your chances of being relevant in today’s tech-world.

The Web3 economy is booming and the best thing you can do for yourself right now is to learn the craft.

How do you develop a smart contract for your Dapp? How do you best design the Dapp interface? How do you connect the frontend to the backend that sits on the blockchain? All these questions will be resolved in this tutorial.

Subscribe to my YouTube channel to learn how to build a Web3 app from scratch.

I also offer private and specialized classes for serious folks who want to learn one-on-one from a mentor. Book your Web3 classes here.

With that said, let’s jump into the tutorial.

Prerequisite

You will need the following tools installed to build along with me:

  • NodeJs (Super important)
  • EthersJs
  • Hardhat
  • React
  • Infuria
  • Tailwind CSS
  • CometChat SDK
  • Metamask
  • Yarn ## Installing Dependencies

Clone the project from the repository below and run the following commands.

git clone https://github.com/Daltonic/genesis <PROJECT_NAME>
cd <PROJECT_NAME>
yarn install
Enter fullscreen mode Exit fullscreen mode

Executing the codes below will add the following dependencies and versions to your project.

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 genesis.

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

The **.env** file should be created in the root of your project.

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 or HTTPs 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 Genesis Smart Contract

Now let’s create the smart contract for this project. Before doing that, you need to understand the scope of this project.

We’re creating a crowdfunding platform where startups and projects of great course can raise funds. This platform help founders with the capital to start up their business, it is called Genesis, which means the BEGINING!

Create a folder called contracts in the src directory of your project. Now, head to src >> contracts and create a file named Genesis.sol and paste the code below inside of it.

Now let’s discuss the content of this smart contract segment by segment.

Defining Essential Variables

address public owner;
uint public projectTax;
uint public projectCount;
uint public balance;

statsStruct public stats;
projectStruct[] projects;

mapping(address => projectStruct[]) projectsOf;
mapping(uint => backerStruct[]) backersOf;
mapping(uint => bool) public projectExist;
Enter fullscreen mode Exit fullscreen mode

In the above block of codes, we defined the various data types and structures for storing data on the blockchain.

For the single data types, we have the owner variable, project tax, count, and the available balance in our smart contract.

For the multi-data types, we have **statsStruct** and **projectStruct** which defines the model of each project and the statistics in our smart contract.

The mapping bear records of the project owners, backers, also the existence of a project.

If you are new to Solidity, I have a full FREE course on YouTube called, Mastering Solidity Basics. So do check it out, like, and subscribe!

Setting up the Essential Structs and Event

enum statusEnum {
    OPEN,
    APPROVED,
    REVERTED,
    DELETED,
    PAIDOUT
}

struct statsStruct {
    uint totalProjects;
    uint totalBacking;
    uint totalDonations;
}

struct backerStruct {
    address owner;
    uint contribution;
    uint timestamp;
    bool refunded;
}

struct projectStruct {
    uint id;
    address owner;
    string title;
    string description;
    string imageURL;
    uint cost;
    uint raised;
    uint timestamp;
    uint expiresAt;
    uint backers;
    statusEnum status;
}

 event Action (
    uint256 id,
    string actionType,
    address indexed executor,
    uint256 timestamp
);
Enter fullscreen mode Exit fullscreen mode

The above code contains structures for declaring enums, structs, and events.
The enum was defined to contain the various states a project can have in our platform.

The **statsStruct** contains the code a statistic should have, such as the total number of donations, backings, and projects.

The **backerStruct** on the other hand contains data types each person backing a project should have. A backer should definitely have an address, how much he is donating, the time of donation, and a refund status to show if the owner was refunded his money.

The **projectStruct** describes what each project must contain and lastly, the event is a dynamic one to output information according to the calling function.

The Constructor and Owner Modifier function

modifier ownerOnly(){
    require(msg.sender == owner, "Owner reserved only");
    _;
}

constructor(uint _projectTax) {
    owner = msg.sender;
    projectTax = _projectTax;
}
Enter fullscreen mode Exit fullscreen mode

Whenever the **ownerOnly()** modifier is attached to a calling function, it restricts its accessibility to the deployer of the smart contract only.

The **constructor()** function on the other hand, initializes the owner state variable along with the tax per project. This tax is what will be charged per an approved project.

The Project Create Function

function createProject(
    string memory title,
    string memory description,
    string memory imageURL,
    uint cost,
    uint expiresAt
    ) public returns (bool) {
    require(bytes(title).length > 0, "Title cannot be empty");
    require(bytes(description).length > 0, "Description cannot be empty");
    require(bytes(imageURL).length > 0, "ImageURL cannot be empty");

    projectStruct memory project;
    project.id = projectCount;
    project.owner = msg.sender;
    project.title = title;
    project.description = description;
    project.imageURL = imageURL;
    project.cost = cost;
    project.timestamp = block.timestamp;
    project.expiresAt = expiresAt;
    projects.push(project);
    projectExist[projectCount] = true;
    projectsOf[msg.sender].push(project);
    stats.totalProjects += 1;

    emit Action (
        projectCount++,
        "PROJECT CREATED",
        msg.sender,
        block.timestamp
    );
    return true;
}
Enter fullscreen mode Exit fullscreen mode

This method takes in project information and pushes it to the project's array. Next, it sets other records such as the statistics and project owners. Lastly, an event is emitted bearing some records of the just created project.

The Project Updation Function

function updateProject(
    uint id,
    string memory title,
    string memory description,
    string memory imageURL,
    uint expiresAt
    ) public returns (bool) {
    require(msg.sender == projects[id].owner, "Unauthorized Entity");
    require(projectExist[id], "Project not found");
    require(bytes(title).length > 0, "Title cannot be empty");
    require(bytes(description).length > 0, "Description cannot be empty");
    require(bytes(imageURL).length > 0, "ImageURL cannot be empty");

    projects[id].title = title;
    projects[id].description = description;
    projects[id].imageURL = imageURL;
    projects[id].expiresAt = expiresAt;

    emit Action (
        id,
        "PROJECT UPDATED",
        msg.sender,
        block.timestamp
    );
    return true;
}
Enter fullscreen mode Exit fullscreen mode

This function updates a project according to the project Id using the title, description, image URL, and expiration time. On completion, it emits a Project Updated event.

The Deletion Function

function deleteProject(uint id) public returns (bool) {
    require(projectExist[id], "Project not found");
    require(projects[id].status == statusEnum.OPEN, "Project no longer opened");
    require(
        msg.sender == projects[id].owner ||
        msg.sender == owner,
        "Unauthorized Entity"
    );

    projects[id].status = statusEnum.DELETED;
    performRefund(id);

    emit Action (
        id,
        "PROJECT DELETED",
        msg.sender,
        block.timestamp
    );
    return true;
}
Enter fullscreen mode Exit fullscreen mode

The function above marks a project as completed, which by so doing, performs a refund operation sending back all the donations to the appropriate donors.

Other Functions
These are the duties of the following functions;

  • PerformPayout(): Releases the contributed money to the project owner as well as paying the platform’s task.
  • PayOutProject(): Performs criteria checks and calls the performPayout() function internally.
  • PerformRefund(): Returns money to the backer of a specific project.
  • RequestRefund(): Performs criteria checks and calls the performRefund() function.
  • BackProject(): Makes donation to a specific project.
  • ChangeTax(): Changes the platform’s task for projects.
  • GetProject(): Returns a particular project detail by Id.
  • GetProjects(): Returns all projects on the smart contract.
  • GetBackers(): Returns a list of backers for a particular project.
  • PayTo(): Sends a specific amount of money to a specific address.

Fantastic, there you have it for the smart contract, now let’s get into merging it with the React frontend.

Configuring the Deployment Script

One more thing to do with the smart contract is to configure the deployment script. Head to the scripts >> deploy.js and paste the codes below inside of it.

const hre = require('hardhat')
const fs = require('fs')

async function main() {
  const taxFee = 5 // unit is in percent
  const Contract = await hre.ethers.getContractFactory('Genesis')
  const contract = await Contract.deploy(taxFee)
  await contract.deployed()
  const address = JSON.stringify({ address: contract.address }, null, 4)

  fs.writeFile('./src/abis/contractAddress.json', address, 'utf8', (err) => {
    if (err) {
      console.error(err)
      return
    }
    console.log('Deployed contract address', contract.address)
  })
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})
Enter fullscreen mode Exit fullscreen mode

The code above when executed compiles the smart contract and deploys it to the specified network.

Reviewing the Hardhat Config File
Head to the root of the project and open up hardhat.config.js.

require("@nomiclabs/hardhat-waffle");
require('dotenv').config()

module.exports = {
  defaultNetwork: "localhost",
  networks: {
    hardhat: {
    },
    localhost: {
      url: "http://127.0.0.1:8545"
    },
    rinkeby: {
      url: process.env.ENDPOINT_URL,
      accounts: [process.env.DEPLOYER_KEY]
    }
  },
  solidity: {
    version: '0.8.11',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  paths: {
    sources: "./src/contracts",
    artifacts: "./src/abis"
  },
  mocha: {
    timeout: 40000
  }
}
Enter fullscreen mode Exit fullscreen mode

Pay heed to the two objects called networks and paths. We specified two networks which are the localhost and the Rinkeby test networks. The localhost is for development purposes and the Rinkeby is for productions/staging purposes.

The environment variables called ENDPOINT_URL and DEVELOPER_KEY were gotten from Infuria and Metamask respectively.

For the path, we are specifying where our smart contracts live and where to dump the generated artifacts. For this case, we specified that they should be kept in the src folder.

Great, lastly spin up your hardhat blockchain server and deploy the smart contract using the commands below.

yarn hardhat node
yarn hardhat run scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode

Developing the Frontend

Let’s put together the various components of the React frontend step by step.

Components
Now create a folder called component in the src directory. This is where all the components will live.

Header Component

Header Component

This component beautifully features two items, the logo which helps you navigate back to the home page and the **connect wallet** button which helps you connect to your Metamask wallet. Create a file named Header.jsx and paste the codes below inside of it.

Hero Component

The Hero Component

This component is responsible for displaying statistics about the projects created on the platform including the total number of backings and donations.

To replicate, create a component called Hero.jsx in the components folder and paste the codes below inside of it.

Projects and ProjectCard components

Projects Component

This component is responsible for rendering individual cards of projects. On the components directory, create a file called Projects.jsx and paste the codes below inside of it.

Again, create another file called **ProjectCard.jsx** still inside of the components folder and paste the following codes in it.

Great, we are moving forward.

AddButton and CreateProject Components

Create Project Component

This component together enables us to create new projects. Good use of Tailwind’s CSS was what it took to craft out that beautiful modal.

In the components folder, create a component called **AddButton.jsx** and paste the codes below in it.

Next, create another component called **CreateProject.jsx** which is configured to be launched by the **AddButton.jsx** component. Paste the codes below within it.

ProjectDetails Component

Project Details

This component is responsible for displaying details of a particular project and also providing some critical buttons to summon other components. To replicate this component, create a file called **ProjectDetails.jsx** in the components directory and paste the codes below in it.

UpdateProject Component

UpdateProject Component

As intuitive as its name sound, this component helps us update projects provided that the user has the right clearance. In the components folder, create a file called **UpdateProject.jsx** and paste the codes below in it.

DeleteProject Component

DeleteProject Component

Like the UpdateProject component, this component can only be utilized if you are the owner of the project or if you are the deployer of the smart contract. Head on to the components folder and create a new file called DeleteProject.jsx and paste the codes below in it.

ChatAuth Component

Chat Auth

This little component is responsible for authenticating users for anonymous chat. Also through this component, owners of projects can create new projects and others can join.

Create a file called **ChatAuth.jsx** and paste the codes below inside of it.

BackProject Component

BackProject Component

This component helps you to specify and donate the number of ethers you are willing to contribute to the project. To replicate, let’s create another component called **BackProject.jsx** and paste the codes in it.

ProjectBackers Component

Project Backers component

This component lists out all the backers of a particular project. Create a file named **ProjectBackers.jsx** and paste the codes inside of it.

Messages Component

Messages Component

This component is responsible for rendering chat content to the view. First, create a file called **Messages.jsx** and paste the following codes inside of it.

The Views
We have three major views on this project, they include the Home, Project, and Chat pages. Let’s have them created one after the other.

On the src directory create a new folder called views and create the following files within it.

Home Page

Home Component

This component bundles the smaller components and services that make up the home page. Create a file in the views directory called **Home.jsx** and paste the codes below in it.

Project View

ProjectPage

There is a lot of logic that goes into the making of this page which can be seen by the number of actionable buttons integrated with it. To implement, create a new file called Project.jsx in the src >> views and paste the codes below inside of it.

Chat View

Chat View

This view allows you to engage in a one-to-many chat using the CometChat SDK. This is how to replicate it, jump into the views directory, create a file called **Chat.jsx**, and paste the codes below inside of it.

The App Component
This mother component bundles and regulates all the pages and components in this application. In the src folder, replace the codes inside the file named **App.jsx** with the one below.

That completes the views and components for this project, now let’s add up the rest of the essential files.

Other Essential Files

The Store Service
We are using a moderate state management library called react-hooks-global-state to store the data coming from the blockchain. Doing this method greatly simplifies the code.

In your src directory, create a folder and a file called store >> index.jsx and paste the codes below inside.

The Blockchain Service
Now, this is one of the most important files in this project, and all the codes to interact with our smart contract are written here. Create a file called **Genesis.jsx** in the src folder and paste the codes below.

The CometChat Services
This file contains all the functions needed to establish chat communications with the CometChat SDK. On your src folder create a file named **CometChat.jsx** and paste the codes below inside.

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

The Index.jsx File
This last step is responsible for initializing your CometChat service. On the src folder, open the **index.jsx** and replace its code with the one below.

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import App from './App'
import { initCometChat } from './CometChat'

initCometChat().then(() => {
  ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('root')
  )
})
Enter fullscreen mode Exit fullscreen mode

And there you have it, you just completed the entire build.

Run the command below to start up your server.

yarn start
Enter fullscreen mode Exit fullscreen mode

Watch my FREE web3 tutorials on YouTube now.

Conclusion

We've reached the end of this Crowd Funding build, and I know you've gained a lot of value from working 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 building!

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.

Top comments (0)