What you will be building, see demo on the Rinkeby test network and git repo here…
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
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.
STEP 2:
Log in to the CometChat dashboard, only after registering.
STEP 3:
From the dashboard, add a new app called genesis.
STEP 4:
Select the app you just created from the list.
STEP 5:
From the Quick Start copy the APP_ID
, REGION
, and AUTH_KEY
, to your .env
file. See the image and code snippet.
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=******************************
The **.env**
file should be created in the root of your project.
Configuring Infuria App
STEP 1:
Head to Infuria, create an account.
STEP 2:
From the dashboard create a new project.
STEP 3:
Copy the Rinkeby
test network WebSocket or HTTPs endpoint URL to your .env
file.
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=******************************
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 2:
Enter your password on the field provided and click the confirm button, this will enable you to access your account private key.
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 4:
Copy your private key to your .env
file. See the image and code snippet below:
ENDPOINT_URL=***************************
SECRET_KEY=******************
DEPLOYER_KEY=**********************
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
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;
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
);
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;
}
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;
}
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;
}
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;
}
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
})
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
}
}
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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')
)
})
And there you have it, you just completed the entire build.
Run the command below to start up your server.
yarn start
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)