Building a Chat DApp with Next.js and Web3.js on the Celo Network

Building a Chat DApp with Next.js and Web3.js on the Celo Network https://celo.academy/uploads/default/optimized/2X/6/6822fc29c867e42179d2bb9beefae16f660934cc_2_1024x576.png
none 0.0 0

Introduction

In this tutorial, we will create a Celo chat dApp using Next.js and Web3.js. The dApp allows users to send and receive messages on the Celo blockchain. We will set up the project by initializing a Next.js project and installing the necessary dependencies. Using Web3.js and ContractKit, we will connect to the Celo network, fetch the user’s accounts, and display them. We defined a chat smart contract with its ABI and contract address. The dApp fetches existing chat messages from the contract and displays them. Users can send messages by calling the sendMessage function on the contract. After sending a message, the dApp updates the list of chat messages. To deploy the chat smart contract on Celo, we will obtain its ABI and address and update them in the dApp. Finally, we will run the Next.js development server, connect a Celo wallet, and interact with the chat dApp. Customizations and additional features can be added to meet specific requirements. Consider error handling, security, and scalability when building a production-ready chat application on the blockchain.

Prerequisite

  • JavaScript or NextJS
  • Celo Blockchain
  • Remix
  • NodeJS and npm Installed
  • Solidity

Project Setup and Dependencies

Let’s start by initializing the NextJS project and install the necessary dependencies required for our chat app to run and make everything work as necessary.

npx create-next-app my-celo-chat-dapp
cd my-celo-chat-dapp
npm install web3@1.5.2 @celo/contractkit

This above command creates a NextJS project with sample code and the right file and directory structures. The third command installs web3js dependencies and Celo SDK required to connect NextJS project to our deployed smart contract.

Adding Necessary NextJS Pages

Edit the pages directory in the project’s root directory. Inside the pages directory, edit a file called index.js and start it with the code below. This will serve as the entry point for your dApp.

  • Step 1: ​​Import the necessary dependencies in index.js
import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import { newKitFromWeb3 } from '@celo/contractkit';
  • Step 2: Set up the initial connection to the Celo network
export default function Home() {
  const [web3, setWeb3] = useState(null);
  const [kit, setKit] = useState(null);
  const [accounts, setAccounts] = useState([]);

  useEffect(() => {
    connectToCelo();
  }, []);

  const connectToCelo = async () => {
    if (window.celo) {
      const web3 = new Web3(window.celo);
      const kit = newKitFromWeb3(web3);
      const accounts = await web3.eth.getAccounts();

      setWeb3(web3);
      setKit(kit);
      setAccounts(accounts);
    } else {
      console.error('Celo wallet not detected');
    }
  };

  // Other parts of the code...
}
  • Step 3: Define the ABI and contract address for the chat smart contract
const CHAT_ABI = [
  // Define the ABI for your chat smart contract here
  // Example: ["function sendMessage(string _message) public"]
];

const CHAT_CONTRACT_ADDRESS = 'YOUR_CHAT_CONTRACT_ADDRESS';

The values for the variables declared above will be coming from the compiled and deployed smart contract which you will see the code in solidity soon.

  • Step 4: Deploy a chat smart contract on the Celo network using Remix. Obtain the ABI and contract address for your deployed chat contract then update the CHAT_ABI and CHAT_CONTRACT_ADDRESS constants in the index.js file with the correct values.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Chat {
    struct Message {
        address sender;
        string content;
    }

    mapping(uint256 => Message) public messages;
    uint256 public messageCount;

    event MessageSent(address indexed sender, string content);

    function sendMessage(string memory _content) public {
        messages[messageCount] = Message(msg.sender, _content);
        messageCount++;
        emit MessageSent(msg.sender, _content);
    }
}

This contract defines a struct Message to store the sender’s address and the message content. The messages mapping holds all the messages, indexed by their respective message IDs. The messageCount variable keeps track of the total number of messages.

The sendMessage function allows users to send a message by providing the content as a parameter. It stores the message in the messages mapping and emits a MessageSent event.

Remember to compile and deploy this smart contract using tools like Remix or Truffle, and obtain the contract address to update the CHAT_CONTRACT_ADDRESS constant in your dApp code.

Feel free to modify and enhance the smart contract according to your specific requirements

  • Step 5: Implement the functionality to fetch and display existing chat messages from the smart contract.
const [chatMessages, setChatMessages] = useState([]);

useEffect(() => {
  fetchChatMessages();
}, []);

const fetchChatMessages = async () => {
  if (!kit) return;

  const chatContract = new kit.web3.eth.Contract(CHAT_ABI, CHAT_CONTRACT_ADDRESS);

  // Fetch the chat messages from the smart contract
  const messageCount = await chatContract.methods.messageCount().call();
  const messages = [];
  for (let i = 0; i < messageCount; i++) {
    const message = await chatContract.methods.messages(i).call();
    messages.push(message);
  }

  setChatMessages(messages);
};
  • Step 6: Update the UI to display the chat messages:
return (
  <div>
    <h1>Celo Chat dApp Example</h1>
    <div>
      {chatMessages.map((msg, index) => (
           <div key={index}>
              <p><b>Sender:</b> {msg.sender}</p>
              <p><b>Content:</b> {msg.content}</p>
        </div>
      ))}
    </div>
    {/* Rest of the code */}
  </div>
);

  • Step 7: Enable users to send messages by calling the sendMessage function on the chat contract
const [message, setMessage] = useState('');

const sendMessage = async () => {
if (!kit || !accounts[0] || !message) return;

const chatContract = new kit.web3.eth.Contract(CHAT_ABI, CHAT_CONTRACT_ADDRESS);

// Call the sendMessage function on the chat contract
await chatContract.methods.sendMessage(message).send({ from: accounts[0] });

// Clear the message input field
setMessage('');

// Fetch updated chat messages
fetchChatMessages();
};
  • Step 8: Update the UI to include an input field and a Send Message button
return (
  <div>
    <h1>Celo Chat dApp Example</h1>
    <div>
      {chatMessages.map((msg, index) => (
          <div key={index}>
              <p><b>Sender:</b> {msg.sender}</p>
             <p><b>Content:</b> {msg.content}</p>
        </div>
      ))}
    </div>
    <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} />
    <button onClick={sendMessage}>Send Message</button>
  </div>
);

Full Code

For easy understanding and make this less ambiguous, I decided to make the whole code a one file codebase. All the code the dApp will run on are only in index.js file and here is how it will look like.

import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import { newKitFromWeb3 } from '@celo/contractkit';

export default function Home() {
  const [web3, setWeb3] = useState(null);
  const [kit, setKit] = useState(null);
  const [accounts, setAccounts] = useState([]);

  useEffect(() => {
    connectToCelo();
  }, []);

  const connectToCelo = async () => {
    if (window.celo) {
      const web3 = new Web3(window.celo);
      const kit = newKitFromWeb3(web3);
      const accounts = await web3.eth.getAccounts();

      setWeb3(web3);
      setKit(kit);
      setAccounts(accounts);
    } else {
      console.error('Celo wallet not detected');
    }
  };

  // Rest of the code...
  const CHAT_ABI = [
    // Define the ABI for your chat smart contract here
    // Example: ["function sendMessage(string _message) public"]
  ];
  
  const CHAT_CONTRACT_ADDRESS = 'YOUR_CHAT_CONTRACT_ADDRESS';

  const [chatMessages, setChatMessages] = useState([]);

useEffect(() => {
  fetchChatMessages();
}, []);

const fetchChatMessages = async () => {
  if (!kit) return;

  const chatContract = new kit.web3.eth.Contract(CHAT_ABI, CHAT_CONTRACT_ADDRESS);

  // Fetch the chat messages from the smart contract
  const messageCount = await chatContract.methods.messageCount().call();
  const messages = [];
  for (let i = 0; i < messageCount; i++) {
    const message = await chatContract.methods.messages(i).call();
    messages.push(message);
  }

  setChatMessages(messages);
};
const [message, setMessage] = useState('');

const sendMessage = async () => {
  if (!kit || !accounts[0] || !message) return;
  
  const chatContract = new kit.web3.eth.Contract(CHAT_ABI, CHAT_CONTRACT_ADDRESS);
  
  // Call the sendMessage function on the chat contract
  await chatContract.methods.sendMessage(message).send({ from: accounts[0] });
  
  // Clear the message input field
  setMessage('');
  
  // Fetch updated chat messages
  fetchChatMessages();
  };

  return (
    <div>
      <h1>Celo Chat dApp Example</h1>
      <div>
        {chatMessages.map((msg, index) => (
          <div key={index}>
              <p><b>Sender:</b> {msg.sender}</p>
             <p><b>Content:</b> {msg.content}</p>
        </div>
        ))}
      </div>
      <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} />
      <button onClick={sendMessage}>Send Message</button>
    </div>
  );
}
  • Step 9: Test your NextJS app locally. Open your web browser and visit http://localhost:3000. You should see the Celo chat dApp with the list of chat messages, input field, and Send Message button. Connect your Celo wallet (e.g., MetaMask) to the Celo network.
npm run dev
  • Step 10: Interact with the dApp

Enter a message in the input field and click the Send Message button. The message will be sent to the chat smart contract on the Celo network, and the list of chat messages will be updated to display the new message. The repository is here! And below is the sample result.

Conclusion

Congratulations! You’ve successfully built a Celo chat dApp using Next.js and Web3.js. Users can send and receive messages on the Celo blockchain through the dApp. Feel free to customise and expand upon this guide based on your specific requirements and use cases. Happy building!

Building a Celo chat dApp using Next.js and Web3.js allows you to create a decentralised messaging application on the Celo blockchain. By following the step-by-step guide, you can connect to the Celo network, interact with smart contracts, and enable users to send and receive messages. Customise the dApp to suit your needs, and consider additional features and optimisations. Embrace the potential of blockchain technology and empower users to communicate securely on the Celo network. Happy coding!

About the Author

Hey, I am Rasheed Mudasiru. A software developer who is passionate about community, open source and developer relations: I write about DevOps, automation, deployments and cloud computing.

4 Likes

Approved for you to get started. You can manage the tutorial here by changing the category to Proposals > In Progress then Proposals > Review as you complete the tutorial. Thanks!

@yafiabiyyu @Kunaldawar who is available to review this

1 Like

@Taiwrash i will be reviewing this

1 Like

nice @4undRaiser waiting for your comments and the way forward. Thanks

1 Like

@Taiwrash your tutorial looks good, but it would be great if you deploy it to vercel and provide the demo link for easy testing. Just like you stated in the README of your tutorial repository.

1 Like

I am managing my vercel free trials. The repository is open. Do well to clone and test it locally or deploy it from your end if you are not comfortable testing locally thanks

1 Like

@Taiwrash I already tested your dapp and i got an error, am just suggesting that it might be a good idea for your readers if there’s a demo link to see the dapp. But you don’t have to do it if you don’t want to.

This happens after sending a message and confirming transaction from the wallet

Note: am using node version 16.19.1

2 Likes

@Taiwrash I was able to fix the error by rendering the components like this instead

{chatMessages.map((msg, index) => (
  <div key={index}>
    <p><b>Sender:</b> {msg.sender}</p>
    <p><b>Content:</b> {msg.content}</p>
  </div>
))}

lemme know when you’re done.

1 Like

Oh! Did you passed the right as arrays or as object ?

I think there’s where the error is coming from.

Send a screenshot of your ABI as it is in your code

1 Like

yea i did that right. I fixed it already

1 Like

maybe you could implement this to prevent your readers from getting the same error

1 Like

Can you push instead. It is open or create a PR

1 Like

Sure I will implement it in the tutorial. Just help push that from your. I will then add it to the tutorial

1 Like

it’s just a two line change. would be faster if you just update the code. Do that let’s move this piece to publish :slightly_smiling_face:

2 Likes

confirm it is perfect now?

1 Like

@4undRaiser see this!

1 Like

@Taiwrash seen!! you go can go ahead and move to publish

1 Like

Done @4undRaiser! Is that all? or is there anything else I need to do?

1 Like

@Taiwrash That’s all

1 Like