Creating an Advanced Celo-Based Job Board Platform with Flutter(Part 1)

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:

  1. Remix IDE: We will use Remix IDE, an open-source tool, to write and deploy our Solidity contracts. You can access Remix IDE here.
  2. 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 unique userId, the userAddress, name, phoneNumber, and a secretKey which is a hashed value used for authentication.
  • Education, Experience, and Certification: These structs store information about a user’s education, work experience, and certifications respectively. They all contain an id and various other properties specific to each category.
  • Applicant: This struct represents a job applicant. It includes a name and an id.
  • Job: This struct represents a job posting. It includes a role, companyName, location, jobType, numberOfApplicants, and jobDescription. It also includes an array of skills required for the job.

State Variables and Mappings

  • counter: This variable is used to keep track of the last issued userId.
  • users: This mapping stores User structs and maps them to the user’s address.
  • userSkills, userEducation, userExperience, and userCertification: These mappings store arrays of the corresponding structs and map them to a userId.
  • jobApplicants: This mapping stores an array of Applicant structs and maps them to a jobId.
  • jobs: This is an array of Job structs that represents all the jobs in the system.

Functions

  • signUp: This function allows a new user to sign up. It generates a userId, creates a new User, and stores it in the users mapping.
  • login: This function allows a user to log in using their address and secret key. It returns the userId.
  • addEducation, removeEducation, addExperience, removeExperience, addCertification, and removeCertification: These functions allow a user to add or remove items from their Education, Experience, and Certification arrays.
  • createJob: This function allows a user to create a new job. It creates a new Job and adds it to the jobs 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 new Applicant and adds it to the jobApplicants mapping.
  • getAllJobs: This function returns all the jobs in the jobs 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).
Screenshot 2023-06-23 at 12.11.42

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:

  1. Enter the address, name, phoneNumber, and secretKey into the corresponding fields.
  2. Click on the signUp button. This will create a new MetaMask transaction.
  3. Confirm the transaction in MetaMask.
  4. After the transaction has been mined, the signUp function will return a new userId.

Login

To test the login function:

  1. Enter the userAddress and secretKey into the corresponding fields.
  2. 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.
  3. 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

References

  1. Solidity Documentation
  2. Remix Documentation
  3. OpenZeppelin Contracts
  4. Source Code
5 Likes

I am somewhat excited about this. First, i’d be excited to see how this turns out

Even more interested in how you get the balance of Demand of Jobs and Supply o available roles. Would be exciting to see how this can work out

2 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

Note: @Qtech, considering the high number of votes received, we’d like to proceed with your proposal. However, it appears very similar to our tutorial available at How to Build A Decentralized Job Board Dapp On The Celo Blockchain Part 1. Please modify the description to make it more distinct, or alternatively, begin with part 2 and demonstrate the process of connecting Flutter to the contract outlined in the article linked here. Kindly ensure that the revised article adheres to our proposal guidelines. Once you have made the necessary revisions, we will conduct an additional review and proceed with reward for this tutorial. Thank you!

2 Likes

Thanks @Celo_Academy

Based on what I saw this tutorial How to Build A Decentralized Job Board Dapp On The Celo Blockchain Part 1, We are only able to post, get and remove a job post.

However what I plan to build is far more robust than this. Users would be able to authenticate, update their profile to add their qualifications and experience, they would be able to apply for a Job with just one click and their profile (Qualifications and Experiences) would be shared with the Job poster, they would be able post a job, and users can search for a job post using keywords

Due to its various features, I plan to write and deploy the smart contract in the part 1 and also give an introduction on how we would be interacting with it using flutter. Then In the part 2 I would build a flutter application and interact fully with the deployed smart contract

2 Likes

I will be reviewing this

1 Like