How to Build an Email Messaging Dapp on Celo

How to Build an Email Messaging Dapp on Celo https://celo.academy/uploads/default/optimized/2X/2/2baab053da7b0740464c22df16ec32618baa62da_2_1024x576.jpeg
none 0.0 0

Introduction​

Celo is a decentralized platform built on the Ethereum blockchain that provides developers with tools to help build scalable Dapps.
Building decentralized applications on the Celo platform provides accessible features to help you build proficient, scalable, and user-friendly Dapps.

This tutorial will get you started with understanding the core features and functionalities of how the Celo blockchain works by walking you through creating a secure decentralized messaging application on the blockchain.
On completing this tutorial, you will be familiar with the working concepts and basic tasks like deployment and interaction with the blockchain.

Prerequisites​

This tutorial expects you to have a solid understanding of the following:

  • Solidity: Solidity is an object-oriented high-level language used for writing smart contracts and is also what you will be using as the primary backend feature for your messaging Dapp.

  • Git/Github: An advanced level of Git/GitHub is not required to work on this project, but You should be familiar with basic git commands and how to navigate features on GitHub.

Requirements​

To get started with this tutorial, you need to have the following tools installed and running:

  • Git: Head over to the link to download the latest version of git.

  • Node Package Manager: You will need a package manager to install multiple packages for this tutorial. Head over to the link to download node, npm.
    Hardhat: This tutorial will also make use of hardhat as its development environment.

Setting up your Workspace

Before getting started with creating the main application, you need to set up the required development environment for this project. Following this tutorial, you will use Hardhat for your smart contract development environment.

  • Here is a link to a starter project you can clone to your preferred local workspace. Run the code:
    git clone git@github.com:Julius170/chatapp-dapp-starter.git

  • Navigate to your terminal and run the command npm install, and you’ll have all the required packages for the application downloaded and ready and running.

The Smart Contract

To get started with creating the smart contract for the chat Dapp complete the following steps below:

  1. First run the command npx hardhat to startup your preinstalled hardhat.
  2. Go to the contract folder in the main directory you’ll see a smaple contract there Lock.sol.
    Inside the folder, rename the solidity file to ChatApp.sol
  3. Copy and paste the code below into the solidity file.
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract ChatApp {
    //USER STRUCT
    struct user {
        string name;
        friend[] friendList;
    }

    struct friend {
        address pubkey;
        string name;
    }

    struct message {
        address sender;
        uint256 timestamp;
        string msg;
    }

    struct AllUserStruck {
        string name;
        address accountAddress;
    }

    AllUserStruck[] getAllUsers;

    mapping(address => user) userList;
    mapping(bytes32 => message[]) allMessages;

    //CHECK USER EXIST
    function checkUserExists(address pubkey) public view returns (bool) {
        return bytes(userList[pubkey].name).length > 0;
    }

    //CREATE ACCOUNT
    function createAccount(string calldata name) external {
        require(checkUserExists(msg.sender) == false, "User already exists");
        require(bytes(name).length > 0, "Username cannot be empty");

        userList[msg.sender].name = name;

        getAllUsers.push(AllUserStruck(name, msg.sender));
    }

    //GET USERNAME
    function getUsername(address pubkey) external view returns (string memory) {
        require(checkUserExists(pubkey), "User is not registered");
        return userList[pubkey].name;
    }

    //ADD FRIENDS
    function addFriend(address friend_key, string calldata name) external {
        require(checkUserExists(msg.sender), "Create an account first");
        require(checkUserExists(friend_key), "User is not registered!");
        require(
            msg.sender != friend_key,
            "Users cannot add themeselves as friends"
        );
        require(
            checkAlreadyFriends(msg.sender, friend_key) == false,
            "These users are already friends"
        );

        _addFriend(msg.sender, friend_key, name);
        _addFriend(friend_key, msg.sender, userList[msg.sender].name);
    }

    //checkAlreadyFriends
    function checkAlreadyFriends(
        address pubkey1,
        address pubkey2
    ) internal view returns (bool) {
        if (
            userList[pubkey1].friendList.length >
            userList[pubkey2].friendList.length
        ) {
            address tmp = pubkey1;
            pubkey1 = pubkey2;
            pubkey2 = tmp;
        }

        for (uint256 i = 0; i < userList[pubkey1].friendList.length; i++) {
            if (userList[pubkey1].friendList[i].pubkey == pubkey2) return true;
        }
        return false;
    }

    function _addFriend(
        address me,
        address friend_key,
        string memory name
    ) internal {
        friend memory newFriend = friend(friend_key, name);
        userList[me].friendList.push(newFriend);
    }

    //GETMY FRIEND
    function getMyFriendList() external view returns (friend[] memory) {
        return userList[msg.sender].friendList;
    }

    //get chat code
    function _getChatCode(
        address pubkey1,
        address pubkey2
    ) internal pure returns (bytes32) {
        if (pubkey1 < pubkey2) {
            return keccak256(abi.encodePacked(pubkey1, pubkey2));
        } else return keccak256(abi.encodePacked(pubkey2, pubkey1));
    }

    //SEND MESSAGE
    function sendMessage(address friend_key, string calldata _msg) external {
        require(checkUserExists(msg.sender), "Create an account first");
        require(checkUserExists(friend_key), "User is not registered");
        require(
            checkAlreadyFriends(msg.sender, friend_key),
            "You are not friend with the given user"
        );

        bytes32 chatCode = _getChatCode(msg.sender, friend_key);
        message memory newMsg = message(msg.sender, block.timestamp, _msg);
        allMessages[chatCode].push(newMsg);
    }

    //READ MESSAGE
    function readMessage(
        address friend_key
    ) external view returns (message[] memory) {
        bytes32 chatCode = _getChatCode(msg.sender, friend_key);
        return allMessages[chatCode];
    }

    function getAllAppUser() public view returns (AllUserStruck[] memory) {
        return getAllUsers;
    }
}

The smart contract above is named ChatApp.sol and represents a simple application on the Ethereum blockchain. Here’s a brief explanation of its functionality:

  • User Structure: The contract defines a user structure that includes a user’s name and a list of their friends.

  • Friend Structure: The friend structure contains the friend’s Ethereum public key (address) and name.

  • Message Structure: The message structure stores the sender’s address, timestamp, and the actual message.

  • AllUserStruck Structure: This structure represents all registered users and includes their names and Ethereum account addresses.

  • Mapping: The contract uses two mappings:

    • userList: Maps user addresses to their corresponding user structure.
    • allMessages: Maps a chat code (derived from the sender and receiver’s addresses) to an array of messages.
  • checkUserExists : checks if a user exists based on their Ethereum public key.

  • createAccount : allows a user to create an account by providing a username. It verifies that the user doesn’t already exist and that the username is not empty.

  • getUsername retrieves the username associated with a given Ethereum public key.

  • addFriend: allows a user to add a friend by providing their friend’s Ethereum public key and name. It verifies that both users exist, are not the same user, and are not already friends.

  • checkAlreadyFriends: checks if two users are already friends based on their public keys.

  • _addFriend: adds a friend to a user’s friend list.

  • getMyFriendList: retrieves the friend list of the calling user.

  • _getChatCode: generates a unique chat code based on the sender and receiver’s addresses.

  • sendMessage: allows a user to send a message to a friend. It verifies that both users exist, are friends, and adds the message to the appropriate chat code.

  • readMessage: retrieves all messages between the calling user and a specific friend based on the chat code.

  • getAllAppUser : returns an array of all registered users’ names and Ethereum account addresses

Deploying your smart contract

  1. Go to the folder Scripts where you will have your deploy script.
  2. Next, inside the file deploy.js and paste the code below.
const hre = require("hardhat");
async function main() {
  const ChatApp = await hre.ethers.getContractFactory("ChatApp");
  const chatApp = await ChatApp.deploy();
  await chatApp.deployed();
  console.log(
    `Contract address ${chatApp.address}`
  );
}
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
  1. Go to your hardhat.config file in the main directory and write replace the code there with the one below.
require("@nomiclabs/hardhat-waffle");
require("dotenv").config({ path: ".env" });
require("hardhat-deploy");
 
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
 
// Prints the Celo accounts associated with the mnemonic in .env
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();
 
  for (const account of accounts) {
    console.log(account.address);
  }
});
 
/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  defaultNetwork: "alfajores",
  networks: {
    localhost: {
      url: "http://127.0.0.1:7545",
    },
    alfajores: {
      gasPrice: 200000000000,
      gas: 41000000000,
      // gasLimit: 40000000,
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0",
      },
      chainId: 44787,
    },
    celo: {
      url: "https://forno.celo.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0",
      },
      chainId: 42220,
    },
  },
  solidity: "0.8.10",
};
  1. Next, create a .env file and create a variable name, MNEMONICS, and head over to your preferred wallet to copy your Mnemonic key or your recovery phrase. Paste the phrase as the value of the Mnemonic variable.

  2. To compile and deploy the contract to the alfajores network, run the command npx hardhat run scripts/deploy.js --network alfajores

Note: Here is a link to the complete application on GitHub

Conclusion​

In conclusion, building a messaging Dapp on Celo is a great way to create a decentralized and secure messaging platform. By following the steps in this article, you can create a messaging Dapp that is user-friendly and functional.

About the Author​

Mayowa Julius Ogungbola
A Software Engineer and Technical writer always open to working on new ideas. I enjoy working on GitHub, and you can also find out what I tweet about and connect with me on LinkedIn.

2 Likes

Will be reviewing this in a day or two @Phenzic

Satisfied moving this in the publish section @Phenzic

I think you meant alfajores, right?

Arigató :clinking_glasses: @EmiriDbest

1 Like