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
andCHAT_CONTRACT_ADDRESS
constants in theindex.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, andSend 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.