Introduction
In the digital era, job searching and recruitment have largely moved online. Building a platform that bridges the gap between job seekers and employers is essential. This tutorial will guide you in creating a job board platform using the power of the Celo blockchain. Our platform will enable employers to post jobs while allowing job seekers to find suitable opportunities. The integration with the Celo blockchain will ensure enhanced security and transparency. In this first part, our focus will be on writing and deploying the smart contract for the job platform.
Prerequisites
Before diving into this tutorial, you should have a basic understanding of blockchain, how Celo operates, and smart contracts. Knowledge of the Solidity programming language will be beneficial as we’ll be using it to write our smart contracts.
Requirements
Before we begin, ensure you have the following:
- Remix IDE: We will use Remix IDE, an open-source tool, to write and deploy our Solidity contracts. You can access Remix IDE here.
- A Celo Wallet: Make sure to set up a wallet on Celo’s Alfajores Testnet. You can do this by following the steps from the
Setting up a Celo Wallet
section in this tutorial.
In the next section, we will delve into the creation and deployment of our Celo smart contract. Let’s get started!
Writing and Deploying the Smart Contract
We’ll be using Remix IDE, an online Ethereum IDE that lets us write Solidity smart contracts and deploy it.
Navigate to Remix Ethereum IDE.
Go to the “File Explorers” tab, and click on the second icon from the left to open the file explorer. Right-click on the contracts
directory button to create a new file and name it HelloWorld.sol.
In the JobBoard.sol
file, write the following Solidity contract code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Strings.sol";
contract JobBoard {
struct User {
string userId;
address userAddress;
string name;
string phoneNumber;
bytes32 secretKey; // Hashed secret key
}
struct Education {
uint id;
string schoolName;
string courseStudied;
string qualification;
string grade;
string startDate;
string endDate;
}
struct Experience {
uint id;
string role;
string companyName;
string companyLocation;
string[] description;
}
struct Certification {
uint id;
string title;
string issuer;
string qualification;
string dateIssued;
}
struct Applicant {
string name;
uint id;
}
struct Job {
uint id;
string userId;
string role;
string companyName;
string location;
string jobType;
uint numberOfApplicants;
string jobDescription;
string[] skills;
}
uint private counter = 0; // counter to keep track of the last issued userId
mapping(address => User) public users;
mapping(string => string[]) public userSkills;
mapping(string => Education[]) public userEducation;
mapping(string => Experience[]) public userExperience;
mapping(string => Certification[]) public userCertification;
mapping(uint => Applicant[]) public jobApplicants;
Job[] public jobs;
function signUp(address userAddress, string memory name, string memory phoneNumber, bytes32 secretKey) public returns(string memory) {
require(users[userAddress].userAddress == address(0), "This address has already been used to sign up");
counter++; // increment counter
// Generate userId using the string prefix and the counter
string memory userId = string(abi.encodePacked("byghyhwksbh2334whb", Strings.toString(counter)));
User memory newUser = User({
userId: userId,
userAddress: userAddress,
name: name,
phoneNumber: phoneNumber,
secretKey: secretKey // Store hashed secret key
});
users[userAddress] = newUser;
return userId; // return the new userId
}
function login(address userAddress, bytes32 secretKey) public view returns (string memory) {
User memory user = users[userAddress];
require(user.userAddress != address(0), "Invalid login details");
require(user.secretKey == secretKey, "Invalid login details");
return user.userId;
}
function addEducation(string memory _userId, Education memory _education) public {
userEducation[_userId].push(_education);
}
function removeEducation(string memory _userId, uint _educationId) public {
delete userEducation[_userId][_educationId];
}
function addExperience(string memory _userId, Experience memory _experience) public {
userExperience[_userId].push(_experience);
}
function removeExperience(string memory _userId, uint _experienceId) public {
delete userExperience[_userId][_experienceId];
}
function addCertification(string memory _userId, Certification memory _certification) public {
userCertification[_userId].push(_certification);
}
function removeCertification(string memory _userId, uint _certificationId) public {
delete userCertification[_userId][_certificationId];
}
function createJob(string memory role, string memory companyName, string memory location, string memory jobType, string memory jobDescription, string[] memory skills) public returns(uint) {
Job memory newJob = Job({
id: jobs.length,
userId: users[msg.sender].userId,
role: role,
companyName: companyName,
location: location,
jobType: jobType,
numberOfApplicants: 0,
jobDescription: jobDescription,
skills: skills
});
jobs.push(newJob);
return jobs.length - 1;
}
function deleteJob(uint jobId) public {
delete jobs[jobId];
}
function applyToJob(uint jobId, string memory applicantName, uint applicantId) public {
Applicant memory newApplicant = Applicant(applicantName, applicantId);
jobApplicants[jobId].push(newApplicant);
jobs[jobId].numberOfApplicants++;
}
function getAllJobs() public view returns (Job[] memory) {
return jobs;
}
}
This smart contract is quite extensive and involves several different components that are tied together. It’s designed to represent a complete job board system, where users can sign up, log in, and interact with job postings.
Here’s a breakdown of the contract:
Structs
The contract includes several data structures (structs) to keep track of various aspects of the system:
User
: This struct stores information about each user, including a uniqueuserId
, theuserAddress
,name
,phoneNumber
, and asecretKey
which is a hashed value used for authentication.Education
,Experience
, andCertification
: These structs store information about a user’s education, work experience, and certifications respectively. They all contain anid
and various other properties specific to each category.Applicant
: This struct represents a job applicant. It includes aname
and anid
.Job
: This struct represents a job posting. It includes arole
,companyName
,location
,jobType
,numberOfApplicants
, andjobDescription
. It also includes an array ofskills
required for the job.
State Variables and Mappings
counter
: This variable is used to keep track of the last issueduserId
.users
: This mapping storesUser
structs and maps them to the user’s address.userSkills
,userEducation
,userExperience
, anduserCertification
: These mappings store arrays of the corresponding structs and map them to auserId
.jobApplicants
: This mapping stores an array ofApplicant
structs and maps them to ajobId
.jobs
: This is an array ofJob
structs that represents all the jobs in the system.
Functions
signUp
: This function allows a new user to sign up. It generates auserId
, creates a newUser
, and stores it in theusers
mapping.login
: This function allows a user to log in using their address and secret key. It returns theuserId
.addEducation
,removeEducation
,addExperience
,removeExperience
,addCertification
, andremoveCertification
: These functions allow a user to add or remove items from theirEducation
,Experience
, andCertification
arrays.createJob
: This function allows a user to create a new job. It creates a newJob
and adds it to thejobs
array.deleteJob
: This function allows a user to delete a job.applyToJob
: This function allows a user to apply for a job. It creates a newApplicant
and adds it to thejobApplicants
mapping.getAllJobs
: This function returns all the jobs in thejobs
array.
The contract also includes import statements for OpenZeppelin’s Strings
library at the top, which is used in the signUp
function to convert the counter
into a string.
In the next sections of the tutorial, we will delve into how to deploy this smart contract to the Celo network.
Compiling and Deploying the Smart Contract
Now, let’s compile the contract. Go to the “Solidity Compiler” tab (fourth icon from the left), select the correct compiler version (0.7.0 in our case), and click on “Compile JobBoard.sol
”.
After successful compilation ( The solidity compiler icon should show a check sign), go to the “Deploy & run transactions” tab (the one with the icon shown below).
Ensure that the Environment is set to “Injected Provider - MetaMask” as shown below. This connects Remix to your Metamask Extension Wallet. Select the JobBoard
contract and click on “Deploy”.
A pop-up from Celo Extension Wallet will open asking you to confirm the transaction.
Confirm it and wait for the transaction to be mined.
Once the transaction is mined, your deployed contract will be available under “Deployed Contracts”
You can copy the contract address of the deployed contract by clicking on the copy icon under the Deployed Contract
section. Keep the contract address handy, we’ll need it in the second part of the tutorial.
Interacting with the Contract in Remix
In the Deploy & Run Transactions panel, you’ll see the deployed contracts under “Deployed Contracts”.
Each function of the contract is represented as a button. You can expand the contract by clicking on it, which will reveal all its functions. These are grouped into two types: view functions that do not change the state of the contract and functions that do change the state.
SignUp
To test the signUp
function:
- Enter the
address
,name
,phoneNumber
, andsecretKey
into the corresponding fields. - Click on the
signUp
button. This will create a new MetaMask transaction. - Confirm the transaction in MetaMask.
- After the transaction has been mined, the
signUp
function will return a new userId.
Login
To test the login
function:
- Enter the
userAddress
andsecretKey
into the corresponding fields. - Click on the
login
button. Since this is a view function (it does not modify the state of the contract), it will not create a MetaMask transaction. - The function will return the
userId
if the login is successful.
Remember to play around with all the other functions of the contract to fully understand how the contract works. This interaction provides a hands-on understanding of how smart contracts function on the Celo network.
In the next part of this tutorial, we will connect this smart contract to a Flutter app using Web3dart
in order to build a fully decentralized Job Board Platform.
Conclusion
Congratulations! You’ve successfully written and deployed a smart contract for a Job Board Platform on the Celo blockchain. We’ve also learned how to interact with the smart contract using the Remix IDE. This is a significant milestone in understanding how Solidity work.
Next Steps
In the next part of this tutorial, we’ll integrate our smart contract with a Flutter application using the web3dart library, allowing users to interact with the blockchain from a mobile application. We will also be creating a user interface for job seekers and employers.
About the Author
Qudus Olanrewaju is a proficient Mobile developer and technical writer who has a strong interest in blockchain technology and web3. He enjoys building web3 projects and leveraging his distinctive viewpoint to create engaging and insightful content. You can connect with me on Linkedin and check out my latest projects on Github