What you will be building, see the live demo at sepolia test net and the git repo.
Learn how to build a decentralized freelance marketplace like Upwork using React, Solidity, and CometChat.
In this step-by-step tutorial, we'll teach you:
Whether you're a seasoned developer or new to Web3, this tutorial will give you the skills to create your own freelance marketplace. Let's get started!
You will need the following tools installed to build along with me:
I recommend watching the videos below to learn how to set up your MetaMask for this project.
Clone the starter kit, and open it in VS Code using the command below:
git clone https://github.com/Daltonic/tailwind_ethers_starter_kit dappworks
cd dappworks
Next, update the package.json
with the snippet below.
Please run the command yarn install
in your terminal to install the dependencies for this project.
To configure the CometChat SDK, please follow the steps provided below. Once completed, make sure to save the generated keys as environment variables for future use.
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 DappWorks
STEP 4: Select the app you just created from the list.
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
placeholder keys with their appropriate values.
REACT_APP_COMETCHAT_APP_ID=****************
REACT_APP_COMETCHAT_AUTH_KEY=******************************
REACT_APP_COMETCHAT_REGION=**
The .env
file should be created at the root of your project.
Navigate to the root directory of the project, and open the "hardhat.config.js
" file. Replace the existing content of the file with the provided settings.
This code configures Hardhat for your project. It includes importing necessary plugins, setting up networks (with localhost as the default), specifying the Solidity compiler version, defining paths for contracts and artifacts, and setting a timeout for Mocha tests.
The following steps will guide you through the process of creating the smart contract file for this project:
contracts
inside the src
folder.Dappworks.sol
inside the contracts
folder.
By following these steps, you will have successfully set up the necessary directory structure and created the Dappworks.sol
file, which will serve as the foundation for implementing the logic of the smart contract.
The DappWorks smart contract is designed to facilitate a decentralized job marketplace, where users can create job listings, place bids on jobs, and manage the entire job lifecycle. Let's explore the key components and functionalities of this contract.
Contract Inheritance and Libraries:
Ownable
and ReentrancyGuard
contracts, which provide access control and protection against reentrancy attacks.
It imports several libraries from the OpenZeppelin framework, including Counters
for managing counters, SafeMath
for secure mathematical operations, and Ownable
and ReentrancyGuard
for access control and reentrancy protection.
State Variables:
The contract includes several state variables to manage job listings, freelancers, and bids. These include counters, structs, and mappings to store relevant data.
Structs:
The contract defines several structs, including JobStruct
, FreelancerStruct
, and BidStruct
, to structure and store information about jobs, freelancers, and bids.
Modifiers:
The contract defines a modifier called onlyJobOwner(uint id)
that restricts certain functions to the owner of a specific job listing.
Functions:
addJobListing
: Allows users to create a new job listing by providing a title, description, and tags. Users must also attach ether as a prize for the job.
deleteJob
: Allows the owner of a job listing to delete it and receive the prize.
updateJob
: Allows the owner of a job listing to update its details.
bidForJob
: Allows users to place bids on job listings.
acceptBid
: Allows the owner of a job listing to accept a specific bid from a freelancer, assigning the job to them.
dispute
: Allows the owner of a job listing to initiate a dispute.
revoke
: Allows the contract owner to revoke an assignment during a dispute.
resolved
: Allows the contract owner to mark a dispute as resolved.
payout
: Allows the owner of a job listing to pay the assigned freelancer, deducting a platform fee.
Various getter functions to retrieve information about jobs, bidders, freelancers, and more.
Internal Functions:
The contract includes internal functions for handling time and making payments.
Events:
The contract emits events to log various actions and state changes.
Constructor:
The contract does not have a constructor, indicating that it can be deployed as is.
Overall, this smart contract is designed to facilitate the creation, management, and completion of job listings on the Ethereum blockchain. Users can create job listings, place bids, and assign jobs to freelancers. It also includes features for dispute resolution and a platform fee for job payouts.
🚀 Blockchain has real use cases, Join Dapp Mentors Academy for $8.44/month to learn all you can.
Subscribe to Dapp Mentors Academy today and get exclusive access to over 40 hours of web3 content, including courses on NFT minting, blockchain development, and more!
The DappWorks test script is thoughtfully crafted to thoroughly evaluate and confirm the functionalities and behaviors of the DappWorks smart contract. Here's an organized breakdown of the primary tests and functions encompassed within the script:
Setup and Initialization:
**toWei**
to convert ether amounts to wei.
It defines a test suite using Mocha's **describe**
function and sets up initial variables and contract instances in the **beforeEach**
hook.
Job Creation Tests:
**should confirm fetching job listings**
: Checks if a job listing can be retrieved and confirms that there is one job listing.**should confirm fetching a single job listing**
: Retrieves a specific job listing and verifies its properties.**should confirm updating of job**
: Tests updating the details of a job listing, and checks if the changes are reflected correctly.**should confirm job deletion**
: Adds and then deletes a job listing, verifying that the job listing count becomes zero.**should confirm bidding for job**
: Tests placing a bid on a job listing and confirms that there is one bidder.**should confirm accepting job bid**
: Places a bid, accepts the bid, and confirms the assignment of the job to a freelancer.**should confirm disputing a job**
: Initiates a dispute for a job listing and verifies that the job is marked as disputed.**should confirm revoking a disputed job**
: Places a bid, accepts the bid, disputes the job, and then revokes it. Checks if the job is listed again and the freelancer's assignment is revoked.**should confirm resolving a disputed job**
: Initiates a dispute and then resolves it, ensuring that the job is no longer marked as disputed.**should confirm payout of a job**
: Places a bid, accepts the bid, and performs a payout, confirming that the job is marked as paid out.
Each test case uses Chai's **expect**
assertion to verify the expected outcomes of various contract interactions. The test script thoroughly tests the functionality of the DappWorks
smart contract, covering job creation, bidding, assignment, dispute handling, payout, and more.
By running **yarn hardhat test**
on the terminal, it will test out all the essential functions of this smart contract.
The DappWorks deployment script is responsible for deploying the DappWorks smart contract to the Ethereum network using the Hardhat development environment. Here's an overview of the script:
Key Components and Functionality
ethers
and fs
dependencies.main()
function to deploy the contract.DappWorks
.contract
variable.address
variable.src/abis/
directory.main()
function.
The DappWorks deployment script simplifies the deployment of the DappWorks smart contract and generates a JSON file, contractAddress.json*
, containing the deployed contract's address. This file can be used for further integration and interaction with the deployed contract within the project.
To utilize this script, create a folder named "scripts" in the root directory of your project if it doesn't already exist. Inside the "scripts" folder, create a JavaScript file named deploy.js
, and then copy and paste the provided deployment script into this file.
Next, run the **yarn hardhat run scripts/deploy.js**
to deploy the smart contract into the network on a terminal.
If you require additional assistance with setting up Hardhat or deploying your Fullstack DApp, I recommend watching this informative video that provides guidance and instructions.
To start developing the frontend of our application, we will create a new folder called components
inside the src
directory. This folder will hold all the components needed for our project.
For each of the components listed below, you will need to create a corresponding file inside the src/components
folder and paste its codes inside it.
The DappWorks header component is a central part of the user interface. It features the DappWorks logo, navigation links for easy access to various app sections, and a wallet connection button. The mobile-friendly design includes a toggle button for a compact menu. This component enhances user navigation and wallet integration.
The JobListingCard
component renders a card displaying information about a job listing, including the title, price, tags, and description. Users can interact with the card to place bids or manage job management tasks.
The component handles user interactions and provides visual feedback through toast notifications. It also checks the user's account status to determine the appropriate actions to display on the card.
Watch my new YouTube tutorial to learn how to integrate RainbowKit's multi-wallet connector into your dApp to improve the quality and user experience.
The CreateJob component enables users to create job listings through a user-friendly modal interface. Users input job details, including title, prize, skills, and description. The component supports up to five featured skills, provides modal controls, and offers transaction feedback via toast notifications.
It seamlessly integrates with global state management for a smooth user experience.
The DeleteJob component is responsible for displaying a confirmation modal when users attempt to delete a job listing. It provides clear visual cues, including a trash icon, a confirmation message, and a warning about irreversible actions.
Users can either cancel or proceed with the deletion, with toast notifications providing feedback on the outcome of the transaction.
The UpdateJob component is responsible for displaying a modal that allows users to update an existing job listing. It retrieves the job details, pre-fills the form fields, and provides an interface for users to make changes.
Upon submission, it triggers the update process on the blockchain and displays toast notifications to indicate the outcome of the transaction.
This component renders a set of actions that an owner of a job listing can perform. It displays information about the job listing, including the title, price, tags, and description.
Depending on the status of the job listing, it allows the owner to perform actions such as updating, deleting, viewing bidders, paying, and chatting with a freelancer.
The component also handles different visual representations based on the state of the job listing, such as showing a "Completed" status when the job has been paid out.
This component displays information about a job listing for potential bidders. It renders the job title, price (in Ethereum), tags, and job description. Additionally, it provides a "Chat with freelancer" button that allows users to initiate a chat with the owner of the job listing.
It serves as a preview of the job for individuals interested in bidding on it.
This component displays information about applicants or bidders for a job listing. It shows the bidder's truncated Ethereum account address, along with buttons for initiating a chat with the bidder and accepting their bid.
The "Chat" button links to a chat page with the bidder, and the "Accept" button allows the job owner to accept the bidder's offer. It provides a user-friendly interface for managing job applications.
The Payout
component is used to facilitate the payout process for a job listing. It displays a modal dialog with an option to initiate a payment. Users can either proceed with the payout or cancel it.
Upon initiating the payout, it communicates with the blockchain using the payout
function and displays toast notifications to inform the user about the transaction status. This component provides a user-friendly interface for handling payouts for completed jobs.
Want to learn how to build an Answer-To-Earn DApp with Next.js, TypeScript, Tailwind CSS, and Solidity?
This video is a great resource for anyone who wants to learn how to build decentralized applications and earn ethers.
Now that we have covered all the components of this application, it is time to start coupling the various pages together. Let's start with the homepage.
To begin developing the pages of our application, we will create a new folder called pages
inside the src
directory. This folder will hold all the pages needed for our project.
For each of the pages listed below, you will need to create a corresponding file inside the src/pages
folder, just as you did before with the components.
The Home
page combines other components, including Header
, Hero
, and CreateJob
, to create the user interface for the home page. The Header
typically contains navigation links and branding, the Hero
section might display introductory content or visuals, and the CreateJob
section allows users to create new job listings.
Overall, this component assembles these parts to create the complete homepage of the application.
The MyProjects
page is where users can manage their own job listings and take various actions on them. It includes components such as Header
for navigation, JobListingOwnerActions
to display and manage job listings, UpdateJob
for updating job details, DeleteJob
for deleting job listings, and Payout
for initiating payments.
Users can view their posted jobs, edit or delete them, and initiate payouts to freelancers. If no jobs are posted, it displays a message indicating that no jobs are available.
The MyJobs
component is where users can view the tasks or jobs that have been assigned to them. It includes a Header
for navigation and a list of JobBid
components that display information about each assigned task.
If there are assigned tasks, it shows them with their details; otherwise, it displays a message indicating that there are no assigned tasks for the user.
The MyBids
page is where users can view the jobs they have applied for by placing bids. It includes a Header
for navigation and a list of JobBid
components that display information about each job the user has bid on.
If there are jobs the user has bid on, it shows them their details; otherwise, it displays a message indicating that the user hasn't bid on any jobs yet.
https://gist.github.com/Daltonic/cd5fd8d3600407e4af98b99d077e4cb8
The ViewBidders
page is where users can view the list of applicants for a specific job listing. It utilizes the useParams
hook to extract the job ID from the URL, fetches the list of bidders and job details from the blockchain, and displays them.
Depending on whether there are applicants or if the position is filled or vacant, it displays relevant information such as applicants' cards or status messages. It also includes a Header
component for navigation.
The Authenticate
page is for user authentication for chatting within the application. It allows users to either log in or sign up for chat functionality using the CometChat service. Upon successful authentication, it displays a success message and navigates the user to the chat messages page.
It also includes a Header
component for navigation and provides feedback to the user through toast notifications.
The "RecentConversations" page in the DappWorks app displays a list of recent chat conversations. It fetches and displays these conversations, allowing users to click on a conversation to navigate to the chat interface with that user.
Each conversation displays the user's name and a unique Identicon for easy identification. If there are no recent chats, a message notifies the user. This page provides a convenient way to access and manage recent chat interactions.
The Chats
page is responsible for managing and displaying chat messages between users. It retrieves and displays messages based on the selected user, allows users to send messages, and automatically updates the chat in real time. Users can see their own messages on the right side and messages from the other user on the left side, with profile icons and message content.
The component also includes a Header
for navigation and maintains a scrolling chat container for easy viewing of messages.
This script is a JavaScript module that interacts with a blockchain smart contract using the Ethereum blockchain. It provides functions for various actions related to the blockchain and stores or retrieves data from the blockchain.
Note that you will have to create a file called blockchain.jsx
within the src >> services
folder, and paste the code below inside.
Here's an overview of what this script does:
It imports necessary dependencies, including contract ABI (Application Binary Interface), contract addresses, and Ethereum-related libraries.
Defines utility functions like toWei
and fromWei
to convert between Ether and Wei.
Defines a function getEthereumContract
to establish a connection to the Ethereum blockchain and retrieve a contract instance.
isWalletConnected
checks if a user's Ethereum wallet (e.g., MetaMask) is connected and handles account changes and chain changes.
Functions like connectWallet
, addJobListing
, updateJob
, deleteJob
, bidForJob
, acceptBid
, dispute
, resolved
, revoke
, payout
, bidStatus
, getBidders
, getFreelancers
, getAcceptedFreelancer
, getJobs
, getMyJobs
, getJob
, getMyBidJobs
, getMyGigs
, and loadData
are defined to interact with the smart contract for various actions such as creating jobs, bidding, accepting bids, and more.
structuredJobs
, structuredBidder
, and structuredFreelancers
functions format data retrieved from the blockchain into structured objects for easier management and display.
Overall, this script acts as an interface between the front-end application and the Ethereum blockchain, allowing users to perform actions and retrieve data related to jobs and bids on the blockchain.
It integrates with a smart contract using the contract's ABI and address. See the script above.
Please ensure that you update the environment variables to look like this:
REACT_APP_COMETCHAT_APP_ID=****************
REACT_APP_COMETCHAT_AUTH_KEY=******************************
REACT_APP_COMETCHAT_REGION=**
REACT_APP_RPC_URL=http://127.0.0.1:8545
This script is a JavaScript module that provides functionality for integrating and interacting with the CometChat Pro service within a web application. CometChat Pro is a chat and messaging platform that allows developers to add real-time chat features to their applications.
Like with the previous service, you will have to create another file called chat.jsx
within the src >> services
folder, and paste the code below inside.
Here is a breakdown of how this script works:
It imports the CometChat library (CometChat
) and a function (getGlobalState
) from a custom store.
It defines constants (CONSTANTS
) for the CometChat application settings, including the App ID, region, and authentication key. These settings are typically stored in environment variables for security.
The initCometChat
function initializes CometChat with the provided application settings. It subscribes to user presence updates and sets the region. Initialization is done asynchronously, and any errors encountered during initialization are caught and logged.
The loginWithCometChat
function performs user login with CometChat. It takes the user's unique identifier (UID) and authentication key as parameters, attempts to log in, and returns a promise that resolves to the user object upon successful login or rejects it with an error in case of failure.
The signUpWithCometChat
function creates a new user on CometChat. It also takes the user's UID and authentication key as parameters, sets the user's name, and returns a promise that resolves to the newly created user upon success or rejects it with an error in case of failure.
The logOutWithCometChat
function logs the current user out of CometChat. It returns a promise that resolves upon successful logout or rejects in case of an error.
The isUserLoggedIn
function checks if a user is currently logged in to CometChat and returns a promise that resolves to the logged-in user or rejects it with an error.
The getUser
function retrieves user information from CometChat based on the provided UID. It returns a promise that resolves to the user object or rejects it with an error.
The getMessages
function fetches messages for a specific user (UID) from CometChat. It sets a message limit and returns a promise that resolves to an array of text messages or rejects with an error.
The sendMessage
function sends a text message to a specified receiver (receiverID) through CometChat. It returns a promise that resolves to the sent message or rejects it with an error.
The getConversations
function retrieves a list of conversations from CometChat. It sets a conversation limit and returns a promise that resolves to the list of conversations or rejects with an error.
The listenForMessage
function sets up a listener to receive incoming text messages in real-time. It takes a listenerID (typically a user's UID) and returns a promise that resolves to the received message or rejects it with an error.
Overall, this script enables the integration of CometChat Pro features into a web application, including user management, message retrieval, sending messages, and real-time message listening. It abstracts the CometChat functionality into convenient functions for use within the application.
Excellent! Now, let's work on the store
file, which serves as a state management library.
This script provides a central state management system for a React application using the react-hooks-global-state
library. It also includes utility functions for text truncation and date formatting.
You will have to create a file called index.jsx
within the src >> store
folder, and paste the code below inside.
Here's an overview of what this script does:
It imports the createGlobalState
function from the react-hooks-global-state
library.
It uses the createGlobalState
function to create a global state container with initial state values.
This global state container is then deconstructed into three functions: setGlobalState
, useGlobalState
, and getGlobalState
.
setGlobalState
: A function used to set or update global state values. It takes a key and a new value as arguments.
useGlobalState
: A custom hook that allows components to access and subscribe to global state values. It returns the current value associated with a specified key and automatically updates the component when that value changes.
getGlobalState
: A function that retrieves the current value associated with a specified key from the global state.
It defines a truncate
function that takes a text string, start and end character counts, and a maximum length as arguments. This function truncates the text and adds ellipses (...) in the middle if the text exceeds the specified maximum length. It's used for shortening text for display.
It defines a formatDate
function that takes a timestamp as an argument and returns a formatted date string in the format "Month Day, Year." This function is used for formatting timestamps into more human-readable date strings.
It defines a timestampToDate
function that takes a timestamp as an argument and returns a formatted date string in the format "Month Day, Year Hour:Minute." This function is used for formatting timestamps into more detailed date and time strings.
Overall, this script serves as a global state management solution for a React application, allowing different components to share and access state data easily. It also provides utility functions for common text and date formatting operations.
The index.jsx
file is the entry point for the application. It initializes the CometChat service, sets up dependencies, and renders the React application using the App
component within a BrowserRouter
. It creates a root element for rendering and sets up the necessary configurations for the application to start running.
To use this code, you will need to replace the code below inside of the index.jsx
and index.css
files in the src
folder of your project.
Now, you are officially done with the build; just execute the following commands to have the application running on the browser.
**yarn hardhat node**
**yarn hardhat run scripts/deploy.js**
and then **yarn start**
Congratulations on building a Web3 Upwork clone with React, Solidity, and CometChat! 🚀 Your achievement is a testament to your prowess in combining these cutting-edge technologies to craft an innovative and engaging application.
By harnessing React for the frontend, Solidity for your smart contracts, and integrating CometChat for real-time communication, you've showcased a versatile skill set in both blockchain development and interactive user experiences.
Your Web3 Upwork clone stands as a remarkable project, bringing together the power of blockchain, modern web development, and real-time communication to create a platform that empowers users and facilitates collaboration in a decentralized manner.
Keep up the great work, and continue exploring the endless possibilities of technology! Your journey in the world of Web3 development is bound to lead to even more exciting and impactful projects in the future.
For more web3 resources, check out this video that teaches how to create a decentralized app by building a web3 lottery dapp; I recommend that you do it.
The video provides a hands-on tutorial on how to build a lottery dapp using NextJs, Tailwind CSS, and Solidity.
Congratulations on completing the journey of building a Web3 Upwork clone with React, Solidity, and CometChat. This comprehensive guide has empowered you to create a cutting-edge platform that combines the power of React for the frontend, Solidity for smart contracts, and CometChat for real-time communication.
By undertaking this project, you've demonstrated your prowess in blockchain development and interactive user experiences.
This tutorial has unveiled the potential of web3 technology in transforming the gig economy, offering secure and transparent transactions while enhancing user engagement through dynamic real-time interactions.
The smart contracts have been rigorously tested to ensure reliability, making your Upwork clone a robust and trustworthy platform for users.
As you conclude this project, you're not only equipped with technical skills but also with a vision of the future where decentralized applications redefine traditional industries. Your Upwork clone is a testament to the possibilities that emerge when innovative technologies converge.
To further enhance your knowledge and stay updated on the latest developments in blockchain and web3, consider subscribing to our YouTube channel and exploring our website for additional resources.
Best wishes on your journey of innovation and discovery in the world of web3 and decentralized applications!
I am a web3 developer and the founder of Dapp Mentors, a company that helps businesses and individuals build and launch decentralized applications. I have over 7 years of experience in the software industry, and I am passionate about using blockchain technology to create new and innovative applications.
I run a YouTube channel called Dapp Mentors where I share tutorials and tips on web3 development, and I regularly post articles online about the latest trends in the blockchain space.
Stay connected with us, and join our communities on
Discord: Join Twitter: Follow LinkedIn: Connect GitHub: Explore Website: Visit