DEV Community

Cover image for How to Build a Transparent and Secure Lottery DApp with NextJs, Solidity, and CometChat
Gospel Darlington
Gospel Darlington

Posted on

3

How to Build a Transparent and Secure Lottery DApp with NextJs, Solidity, and CometChat

What you will be building, see the live demo and the git repo.

Purchasing a Ticket

CometChat One-to-many chat

Introduction

If you're looking to build a cutting-edge decentralized application that combines the power of blockchain technology, real-time communication, and user-generated content, then this tutorial on building a Lottery DApp with NextJs, Solidity, and CometChat is for you.

Whether you're an experienced developer or just starting out, this step-by-step guide will walk you through the process of creating a fair and transparent lottery system on the blockchain. So why not start building your own Lottery DApp today and disrupt the traditional gambling industry?

And if you're interested in learning more about Web3 development, don't forget to subscribe to my YouTube channel and check out my premium web3 content and services.

Now, let’s jump into this tutorial.

Prerequisites

You will need the following tools installed to build along with me:

  • Nodejs (Important)
  • EthersJs
  • Hardhat
  • Redux toolkit
  • Yarn
  • Metamask
  • NextJs
  • Tailwind CSS
  • CometChat SDK

To set up your Metamask for this project, I recommend that you can watch the video below.

Installing Dependencies

Clone the starter kit and open it in VS Code using the command below:

git clone https://github.com/Daltonic/tailwind_ethers_starter_kit <PROJECT_NAME>
cd <PROJECT_NAME>
Enter fullscreen mode Exit fullscreen mode
{
"name": "dapplottery",
"description": "A Next.js starter that includes all you need to build amazing projects",
"version": "1.0.0",
"private": true,
"author": "darlington gospel<darlingtongospel@gmail.com>",
"license": "MIT",
"keywords": [
"nextjs",
"starter",
"typescript"
],
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"export": "next build && next export",
"lint": "next lint",
"format": "prettier --ignore-path .gitignore \"pages/**/*.+(ts|js|tsx)\" --write",
"postinstall": "husky install"
},
"lint-staged": {
"./src/**/*.{ts,js,jsx,tsx}": [
"yarn lint --fix",
"yarn format"
]
},
"dependencies": {
"@cometchat-pro/chat": "3.0.11",
"@reduxjs/toolkit": "1.9.3",
"ethers": "^5.4.7",
"next": "13.1.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "4.8.0",
"react-identicons": "1.2.5",
"react-redux": "8.0.5",
"react-toastify": "9.1.2"
},
"devDependencies": {
"@emotion/react": "11.10.5",
"@emotion/styled": "11.10.5",
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@faker-js/faker": "7.6.0",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@nomiclabs/hardhat-waffle": "2.0.3",
"@openzeppelin/contracts": "4.8.1",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.1",
"autoprefixer": "10.4.13",
"chai": "^4.2.0",
"dotenv": "16.0.3",
"eslint": "8.32.0",
"eslint-config-alloy": "4.9.0",
"eslint-config-next": "13.1.2",
"hardhat": "2.12.7",
"hardhat-gas-reporter": "^1.0.8",
"husky": "8.0.3",
"lint-staged": "13.1.0",
"postcss": "8.4.21",
"prettier": "2.8.3",
"solidity-coverage": "^0.8.0",
"tailwindcss": "3.2.4",
"typechain": "^8.1.0",
"typescript": "4.9.4"
}
}
view raw package.json hosted with ❤ by GitHub

Now, run **yarn install** on the terminal to have all the dependencies for this project installed.

Configuring CometChat SDK

Follow the steps below to configure the CometChat SDK; at the end, you must save these keys as an environment variable.

STEP 1:
Head to CometChat Dashboard and create an account.

Register a new CometChat account if you do not have one

STEP 2:
Log in to the CometChat dashboard, only after registering.

Log in to the CometChat Dashboard with your created account

STEP 3:
From the dashboard, add a new app called DappLottery.

Create a new CometChat app - Step 1

Create a new CometChat app - Step 2

STEP 4:
Select the app you just created from the list.

Select your created app

STEP 5:
From the Quick Start copy the APP_ID, REGION, and AUTH_KEY, to your .env.local file. See the image and code snippet.

Copy the the APP_ID, REGION, and AUTH_KEY

Replace the REACT_COMET_CHAT placeholder keys with their appropriate values.

REACT_APP_COMETCHAT_APP_ID=****************
REACT_APP_COMETCHAT_AUTH_KEY=******************************
REACT_APP_COMETCHAT_REGION=**
Enter fullscreen mode Exit fullscreen mode

The .env.local file should be created at the root of your project.

Configuring the Hardhat script

At the root of this project, open the hardhat.config.js file and replace its content with the following settings.

require('@nomiclabs/hardhat-waffle')
require('dotenv').config()
module.exports = {
defaultNetwork: 'localhost',
networks: {
hardhat: {},
localhost: {
url: 'http://127.0.0.1:8545',
},
},
solidity: {
version: '0.8.17',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
mocha: {
timeout: 40000,
},
}

The above script instructs hardhat on these two important rules.

  • Networks: This block contains the configurations for your choice of networks. On deployment, hardhat will require you to specify a network for shipping your smart contracts.

  • Solidity: This describes the version of the compiler to be used by hardhat for compiling your smart contract codes into bytecodes and abi.

Configuring the Deployment Script

Navigate to the scripts folder and then to your deploy.js file and paste the code below into it. If you can't find a script folder, make one, create a deploy.js file, and paste the following code into it.

const { ethers } = require('hardhat')
const fs = require('fs')
async function main() {
const servicePercent = 7
const Contract = await ethers.getContractFactory('DappLottery')
const contract = await Contract.deploy(servicePercent)
await contract.deployed()
const address = JSON.stringify({ address: contract.address }, null, 4)
fs.writeFile('./artifacts/contractAddress.json', address, 'utf8', (err) => {
if (err) {
console.error(err)
return
}
console.log('Deployed contract address', contract.address)
})
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
view raw deploy.js hosted with ❤ by GitHub

When run as a Hardhat deployment command, the above script will deploy your specified smart contract to the network of your choice.

If you are struggling with a low spec computer, or you want to do some web3 coding on the fly, check out this video to learn how to properly set up a web3 project with Gitpod.

The Smart Contract File

Now that we've completed the initial configurations, let's create the smart contract for this project. Create a new folder called **contracts** in your project's root.

Create a new file called **Dapp****Lottery.sol** within this contracts' folder; this file will contain all the logic that governs the smart contract.

Copy, paste, and save the following codes into the **Dapp****Lottery.sol** file. See the complete code below.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract DappLottery is Ownable {
using Counters for Counters.Counter;
Counters.Counter private _totalLotteries;
struct LotteryStruct {
uint256 id;
string title;
string description;
string image;
uint256 prize;
uint256 ticketPrice;
uint256 participants;
bool drawn;
address owner;
uint256 createdAt;
uint256 expiresAt;
}
struct ParticipantStruct {
address account;
string lotteryNumber;
bool paid;
}
struct LotteryResultStruct {
uint256 id;
bool completed;
bool paidout;
uint256 timestamp;
uint256 sharePerWinner;
ParticipantStruct[] winners;
}
uint256 public servicePercent;
uint256 public serviceBalance;
mapping(uint256 => LotteryStruct) lotteries;
mapping(uint256 => ParticipantStruct[]) lotteryParticipants;
mapping(uint256 => string[]) lotteryLuckyNumbers;
mapping(uint256 => mapping(uint256 => bool)) luckyNumberUsed;
mapping(uint256 => LotteryResultStruct) lotteryResult;
constructor(uint256 _servicePercent) {
servicePercent = _servicePercent;
}
function createLottery(
string memory title,
string memory description,
string memory image,
uint256 prize,
uint256 ticketPrice,
uint256 expiresAt
) public {
require(bytes(title).length > 0, "title cannot be empty");
require(bytes(description).length > 0, "description cannot be empty");
require(bytes(image).length > 0, "image cannot be empty");
require(prize > 0 ether, "prize cannot be zero");
require(ticketPrice > 0 ether, "ticketPrice cannot be zero");
require(
expiresAt > block.timestamp,
"expireAt cannot be less than the future"
);
_totalLotteries.increment();
LotteryStruct memory lottery;
lottery.id = _totalLotteries.current();
lottery.title = title;
lottery.description = description;
lottery.image = image;
lottery.prize = prize;
lottery.ticketPrice = ticketPrice;
lottery.owner = msg.sender;
lottery.createdAt = block.timestamp;
lottery.expiresAt = expiresAt;
lotteries[lottery.id] = lottery;
}
function importLuckyNumbers(uint256 id, string[] memory luckyNumbers)
public
{
require(lotteries[id].owner == msg.sender, "Unauthorized entity");
require(lotteryLuckyNumbers[id].length < 1, "Already generated");
require(lotteries[id].participants < 1, "Tickets have been purchased");
require(luckyNumbers.length > 0, "Lucky numbers cannot be zero");
lotteryLuckyNumbers[id] = luckyNumbers;
}
function buyTicket(uint256 id, uint256 luckyNumberId) public payable {
require(
!luckyNumberUsed[id][luckyNumberId],
"Lucky number already used"
);
require(
msg.value >= lotteries[id].ticketPrice,
"insufficient ethers to buy ethers"
);
lotteries[id].participants++;
lotteryParticipants[id].push(
ParticipantStruct(
msg.sender,
lotteryLuckyNumbers[id][luckyNumberId],
false
)
);
luckyNumberUsed[id][luckyNumberId] = true;
serviceBalance += msg.value;
}
function randomlySelectWinners(
uint256 id,
uint256 numOfWinners
) public {
require(
lotteries[id].owner == msg.sender ||
lotteries[id].owner == owner(),
"Unauthorized entity"
);
require(!lotteryResult[id].completed, "Lottery have already been completed");
require(
numOfWinners <= lotteryParticipants[id].length,
"Number of winners exceeds number of participants"
);
// Initialize an array to store the selected winners
ParticipantStruct[] memory winners = new ParticipantStruct[](numOfWinners);
ParticipantStruct[] memory participants = lotteryParticipants[id];
// Initialize the list of indices with the values 0, 1, ..., n-1
uint256[] memory indices = new uint256[](participants.length);
for (uint256 i = 0; i < participants.length; i++) {
indices[i] = i;
}
// Shuffle the list of indices using Fisher-Yates algorithm
for (uint256 i = participants.length - 1; i >= 1; i--) {
uint256 j = uint256(
keccak256(abi.encodePacked(block.timestamp, i))
) % (i + 1);
uint256 temp = indices[j];
indices[j] = indices[i];
indices[i] = temp;
}
// Select the winners using the first numOfWinners indices
for (uint256 i = 0; i < numOfWinners; i++) {
winners[i] = participants[indices[i]];
lotteryResult[id].winners.push(winners[i]);
}
lotteryResult[id].id = id;
lotteryResult[id].completed = true;
lotteryResult[id].timestamp = block.timestamp;
payLotteryWinners(id);
}
function payLotteryWinners(uint256 id) internal {
ParticipantStruct[] memory winners = lotteryResult[id].winners;
uint256 totalShares = lotteries[id].ticketPrice * lotteryParticipants[id].length;
uint256 platformShare = (totalShares * servicePercent) / 100 ;
uint256 netShare = totalShares - platformShare;
uint256 sharesPerWinner = netShare / winners.length;
for (uint256 i = 0; i < winners.length; i++)
payTo(winners[i].account, sharesPerWinner);
payTo(owner(), platformShare);
serviceBalance -= totalShares;
lotteryResult[id].paidout = true;
lotteryResult[id].sharePerWinner = sharesPerWinner;
}
function getLotteries() public view returns (LotteryStruct[] memory Lotteries) {
Lotteries = new LotteryStruct[](_totalLotteries.current());
for (uint256 i = 1; i <= _totalLotteries.current(); i++) {
Lotteries[i - 1] = lotteries[i];
}
}
function getLottery(uint256 id) public view returns (LotteryStruct memory) {
return lotteries[id];
}
function getLotteryParticipants(uint256 id) public view returns (ParticipantStruct[] memory) {
return lotteryParticipants[id];
}
function getLotteryLuckyNumbers(uint256 id) public view returns (string[] memory) {
return lotteryLuckyNumbers[id];
}
function getLotteryResult(uint256 id) public view returns (LotteryResultStruct memory) {
return lotteryResult[id];
}
function payTo(address to, uint256 amount) internal {
(bool success, ) = payable(to).call{value: amount}("");
require(success);
}
}
view raw DappLottery.sol hosted with ❤ by GitHub

I have a book to help you master the web3 language (Solidity), grab your copy here.

Capturing Smart Contract Development

Now, let's go over some of the details of what's going on in the smart contract above. We have the following items:

This is a Solidity smart contract named "DappLottery" that enables the creation of a lottery where users can purchase tickets and participate in a chance to win a prize. The smart contract has several functions that perform different tasks:

  1. **Ownable**: This is an imported contract from OpenZeppelin that provides a basic access control mechanism to restrict access to certain functions to the contract owner only.

  2. **Counters**: This is an imported contract from OpenZeppelin that provides a way to keep track of the total number of lotteries created.

  3. **LotteryStruct**: This is a struct that defines the properties of a lottery, such as **id**, **title**, **description**, **image**, **prize**, **ticketPrice**, **participants**, **drawn**, **owner**, **createdAt**, and **expiresAt**.

  4. **ParticipantStruct**: This is a struct that defines the properties of a participant, such as **account**, **lotteryNumber**, and **paid**.

  5. **LotteryResultStruct**: This is a struct that defines the properties of a lottery result, such as **id**, **completed**, **paidout**, **timestamp**, **sharePerWinner**, and an array of **winners**, which are of type **ParticipantStruct**.

  6. **servicePercent** and **serviceBalance**: These are state variables that represent the percentage of service fee charged per lottery and the total balance earned from service fees, respectively.

  7. **lotteries**, **lotteryParticipants**, **lotteryLuckyNumbers**, **luckyNumberUsed**, and **lotteryResult**: These are mappings used to store and retrieve data related to lotteries, their participants, lucky numbers, lottery results, and whether a lucky number has been used.

The following are the functions provided by this smart contract:

  1. **constructor(uint256 _servicePercent)**: This is the constructor function that initializes the **servicePercent** variable with the percentage of service fee charged per lottery.

  2. **createLottery()**: This function allows the creation of a new lottery with a **title**, **description**, **image**, **prize**, **ticketPrice**, and **expiresAt**. It also checks for some conditions before creating the lottery such as ensuring that the **title**, **description**, and **image** are not empty, **prize** and **ticketPrice** are not zero, and **expiresAt** is in the future.

  3. **importLuckyNumbers()**: This function allows the owner of a lottery to import a list of **luckyNumbers** that will be used to select the winners of the lottery. It checks for some conditions before importing the list, such as ensuring that the **luckyNumbers** are not empty, and that the lottery does not have any participants yet.

  4. **buyTicket()**: This function allows users to buy tickets for a lottery by specifying the **id** of the lottery and the **luckyNumberId** they want to use. It checks for some conditions before allowing the purchase, such as ensuring that the lucky number has not been used before and that the user has provided enough funds to purchase the ticket.

  5. **randomlySelectWinners()**: This function selects the winners of a lottery randomly from the list of participants using the Fisher-Yates algorithm. It checks for some conditions before selecting the winners, such as ensuring that the lottery has not been completed, and that the number of winners selected does not exceed the number of participants.

  6. **payLotteryWinners()**: This is an internal function that pays out the winners of a lottery by calculating the share of the prize each winner is entitled to, and then transferring the appropriate amount of funds to each winner's account.

Overall, this smart contract provides a simple and secure way to create and manage lotteries on the Ethereum blockchain. It ensures transparency and fairness in the selection of winners, and automates the payment process to reduce the risk of fraud or errors.

Next, run the commands below to deploy the smart contract into the network.

yarn hardhat node # Terminal #1
yarn hardhat run scripts/deploy.js # Terminal #2
Enter fullscreen mode Exit fullscreen mode

Activities of Deployment on the Terminal

If you need further help to configure Hardhat or deploying your Fullstack DApp, watch this video. It will teach you how to do an Omini-chain deployment.

Developing the Frontend

Now that we have our smart contract on the network and all of our artifacts (bytecodes and ABI) generated, let's get the front end ready with React.

Components

In the root directory, create a new folder called **components** to house all the NextJs components for this project. For each one of the components below, you will have to create their respective files in the components' folder.

Header and Sub-header components

Header Component

Sub-header Component

This component contains the logo, dummy navigational elements, and a connect wallet button, see the code below.

import networking from '../assets/networking.png'
import background from '../assets/background.jpg'
import Image from 'next/image'
import { useSelector } from 'react-redux'
import { connectWallet, truncate } from '@/services/blockchain'
import Link from 'next/link'
const Header = () => {
const { wallet } = useSelector((state) => state.globalState)
return (
<div
className="px-5 md:px-40"
style={{ background: `url('${background.src}') fixed no-repeat top/cover` }}
>
<div className="flex items-center justify-between text-white py-5">
<div>
<h1 className="text-xl font-bold">DappLottery</h1>
</div>
<div className="hidden lg:flex items-center space-x-3 font-semibold">
<p>Home</p>
<p>How To Play</p>
<p>All Lottery</p>
<p>Contact</p>
</div>
{wallet ? (
<button
className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 cursor-pointer font-semibold text-sm"
>
{truncate(wallet, 4, 4, 11)}
</button>
) : (
<button
onClick={connectWallet}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 cursor-pointer font-semibold text-sm"
>
Connect Wallet
</button>
)}
</div>
<div className="flex items-center justify-between pb-5">
<div>
<div className="text-white py-5">
<h2 className="text-4xl font-bold py-4 ">
Take the chance to <br /> change your life
</h2>
<p className="text-xl">
We bring a persolan and effective to every project we work on. <br />
Which is why our client love why they keep coming back.
</p>
</div>
</div>
<div className="py-5 hidden sm:block">
<Image src={networking} alt="network" className="rounded-lg w-80" />
</div>
</div>
<div className="pb-10">
<Link
href={'/create'}
className="bg-amber-500 hover:bg-rose-600 text-white rounded-md
cursor-pointer font-semibold py-3 px-5"
>
Create Jackpot
</Link>
</div>
</div>
)
}
export default Header
view raw Header.jsx hosted with ❤ by GitHub
import Link from 'next/link'
import background from '@/assets/background.jpg'
import { useSelector } from 'react-redux'
import { connectWallet, truncate } from '@/services/blockchain'
const SubHeader = () => {
const { wallet } = useSelector((state) => state.globalState)
return (
<div
style={{ background: `url('${background.src}') fixed no-repeat top/cover` }}
className="flex items-center justify-between text-white px-10 py-5"
>
<div>
<Link href="/" className="text-xl font-bold">
DappLottery
</Link>
</div>
<div className="hidden lg:flex items-center space-x-6 font-semibold">
<p>Home</p>
<p>How To Play</p>
<p>All Lottery</p>
<p>Contact</p>
</div>
{wallet ? (
<button
className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 cursor-pointer font-semibold text-sm"
>
{truncate(wallet, 4, 4, 11)}
</button>
) : (
<button
onClick={connectWallet}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 cursor-pointer font-semibold text-sm"
>
Connect Wallet
</button>
)}
</div>
)
}
export default SubHeader
view raw SubHeader.jsx hosted with ❤ by GitHub

Within the component's folder, create two files, Header.jsx and SubHeader.jsx respectively, and paste the above codes into it.

Jackpots Component

Jackpot component

This component was built to display the cards in grid view, as can be seen in the image above, see the codes below to understand how to recreate it.

import Link from 'next/link'
import Image from 'next/image'
import { truncate } from '@/services/blockchain'
const Jackpots = ({ jackpots }) => {
return (
<div className="bg-slate-100 pt-5">
<div className=" flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold text-slate-800 py-5">Lottery Jackpots</h1>
<p className="text-center text-sm text-slate-600">
We bring a persolan and effective every project we work on. <br />
which is why our client love why they keep coming back.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6 md:gap-4 lg:gap-3 py-10 w-4/5 mx-auto">
{jackpots?.map((jackpot, i) => (
<Jackpot jackpot={jackpot} key={i} />
))}
</div>
</div>
)
}
const Jackpot = ({ jackpot }) => {
return (
<div className="w-full shadow-xl shadow-black rounded-md overflow-hidden bg-gray-800 my-2 px-3 py-5">
<div className="flex justify-start items-center space-x-2">
<Image
width={100}
height={512}
src={jackpot.image}
alt="icon"
className="rounded-lg w-20"
/>
<div>
<p className="text-green-300">Upto: {jackpot.prize} ETH</p>
<p className="text-sm text-gray-500">Draws On: {jackpot.drawsAt}</p>
</div>
</div>
<div className="py-5">
<p className="font-semibold pb-2 text-green-300">{jackpot.title}</p>
<p className="text-sm leading-5 text-gray-500">{truncate(jackpot.description, 90, 3, 0)}</p>
</div>
<Link
href={'/jackpots/' + jackpot.id}
className="bg-green-500 hover:bg-rose-600 py-2 px-5
rounded-md text-white font-semibold"
>
PLAY NOW
</Link>
</div>
)
}
export default Jackpots
view raw Jackpots.jsx hosted with ❤ by GitHub

Countdown Component

Countdown Component

This component accepts a timestamp and renders a countdown that counts from days to seconds. See the codes below.

import { useState, useEffect } from 'react'
const Countdown = ({ timestamp }) => {
const [timeLeft, setTimeLeft] = useState(timestamp - Date.now())
useEffect(() => {
const interval = setInterval(() => {
setTimeLeft(timestamp - Date.now())
}, 1000)
return () => clearInterval(interval)
}, [timestamp])
const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24))
const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000)
return timestamp && Date.now() < timestamp ? (
<div className="flex items-center justify-center space-x-3 flex-wrap">
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">{days}</p>
<p className="text-xs font-semibold">DAYS</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">{hours}</p>
<p className="text-xs font-semibold">HOURS</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">{minutes}</p>
<p className="text-xs font-semibold">MINUTES</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">{seconds}</p>
<p className="text-xs font-semibold">SECONDS</p>
</div>
</div>
) : (
<div className="flex items-center justify-center space-x-3 flex-wrap">
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">00</p>
<p className="text-xs font-semibold">DAYS</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">00</p>
<p className="text-xs font-semibold">HOURS</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">00</p>
<p className="text-xs font-semibold">MINUTES</p>
</div>
<div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
<p className="text-3xl text-gray-600 -light">00</p>
<p className="text-xs font-semibold">SECONDS</p>
</div>
</div>
)
}
export default Countdown
view raw Countdown.jsx hosted with ❤ by GitHub

Draw Time Component

The Draw Time Component

This component displays the details of a lottery, some buttons to generate lottery numbers, create group chat with, see the lottery result page, and login to the chat interface. Lastly, it contains a table to render all generated lottery numbers. See the codes below.

import Link from 'next/link'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { FaEthereum } from 'react-icons/fa'
import Countdown from '@/components/Countdown'
import { buyTicket } from '@/services/blockchain'
import { useDispatch, useSelector } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { createNewGroup, joinGroup } from '@/services/chat'
const DrawTime = ({ jackpot, luckyNumbers, participants }) => {
const { setGeneratorModal, setAuthModal, setChatModal, setGroup } = globalActions
const { wallet, currentUser, group } = useSelector((state) => state.globalState)
const dispatch = useDispatch()
const router = useRouter()
const { jackpotId } = router.query
const { CometChat } = window
const handlePurchase = async (luckyNumberId) => {
if (!wallet) return toast.warning('Connect your wallet')
await toast.promise(
new Promise(async (resolve, reject) => {
await buyTicket(jackpotId, luckyNumberId, jackpot?.ticketPrice)
.then(async () => {
resolve()
})
.catch(() => reject())
}),
{
pending: 'Approve transaction...',
success: 'Ticket purchased successfully 👌',
error: 'Encountered error 🤯',
}
)
}
const handleGroupCreation = async () => {
if (!currentUser) return toast.warning('Please authenticate chat')
await toast.promise(
new Promise(async (resolve, reject) => {
await createNewGroup(CometChat, `guid_${jackpot?.id}`, jackpot?.title)
.then((group) => {
dispatch(setGroup(JSON.parse(JSON.stringify(group))))
resolve()
})
.catch(() => reject())
}),
{
pending: 'Creating group...',
success: 'Group created successfully 👌',
error: 'Encountered error 🤯',
}
)
}
const handleGroupJoin = async () => {
if (!currentUser) return toast.warning('Please authenticate chat')
await toast.promise(
new Promise(async (resolve, reject) => {
await joinGroup(CometChat, `guid_${jackpot?.id}`)
.then((group) => {
dispatch(setGroup(JSON.parse(JSON.stringify(group))))
resolve()
window.location.reload()
})
.catch(() => reject())
}),
{
pending: 'Joining group...',
success: 'Group joined successfully 👌',
error: 'Encountered error 🤯',
}
)
}
const onGenerate = () => {
if (luckyNumbers.length > 0) return toast.warning('Already generated')
dispatch(setGeneratorModal('scale-100'))
}
return (
<div className="py-10 px-5 bg-slate-100">
<div className="flex flex-col items-center justify-center text-center py-10">
<h4 className="text-4xl text-slate-700 text-center font-bold pb-3">
Buy Lottery Tickets Online
</h4>
<p className="text-lg text-gray-600 font-semibold capitalize">{jackpot?.title}</p>
<p className="text-sm text-gray-500 w-full sm:w-2/3">{jackpot?.description}</p>
<p className="text-sm font-medium text-black w-full sm:w-2/3">
{jackpot?.participants} participants
</p>
</div>
<div className="flex flex-col justify-center items-center space-y-4 mb-6">
{jackpot?.expiresAt ? <Countdown timestamp={jackpot?.expiresAt} /> : null}
<div className="flex justify-center items-center space-x-2">
{wallet?.toLowerCase() == jackpot?.owner ? (
<>
<button
disabled={jackpot?.expiresAt < Date.now()}
onClick={onGenerate}
className={`flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 font-semibold
${
jackpot?.expiresAt < Date.now()
? 'opacity-50 cursor-not-allowed'
: 'hover:bg-rose-600'
}
`}
>
Generate Lucky Numbers
</button>
{!group ? (
<button
onClick={handleGroupCreation}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-gray-500
hover:bg-rose-600 font-semibold text-white"
>
Create Group
</button>
) : null}
</>
) : group && !group.hasJoined ? (
<button
onClick={handleGroupJoin}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-gray-500
hover:bg-rose-600 font-semibold text-white"
>
Join Group
</button>
) : null}
<Link
href={`/results/` + jackpot?.id}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-[#0c2856]
hover:bg-[#1a396c] cursor-pointer font-semibold text-white"
>
Draw Result
</Link>
{!currentUser ? (
<button
onClick={() => dispatch(setAuthModal('scale-100'))}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-green-500
hover:bg-amber-600 font-semibold"
>
Login Chat
</button>
) : (
<button
onClick={() => dispatch(setChatModal('scale-100'))}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-green-500
hover:bg-amber-600 font-semibold"
>
Enter Chat
</button>
)}
</div>
</div>
<div className="bg-white text-sm overflow-x-auto flex flex-col w-full sm:w-3/4 mx-auto p-5 rounded-md">
<div className="pb-4 text-center">
<p className="semibold text-2xl">Select Your winning Lottery Numbers</p>
</div>
<table className="table-auto">
<thead className="max-h-80 overflow-y-auto block">
<tr className="flex justify-between text-left">
<th className="px-4 py-2 ">#</th>
<th className="px-4 py-2 ">Ticket Price</th>
<th className="px-4 py-2 ">Draw Date</th>
<th className="px-4 py-2 ">Ticket Number</th>
<th className="px-4 py-2 ">Action</th>
</tr>
</thead>
<tbody className="max-h-80 overflow-y-auto block">
{luckyNumbers?.map((luckyNumber, i) => (
<tr className="flex justify-between border-b text-left" key={i}>
<td className="px-4 py-2 font-semibold">{i + 1}</td>
<td className="px-4 py-2 font-semibold">
<div className="flex justify-center items-center space-x-1">
<FaEthereum />
<span>{jackpot?.ticketPrice}</span>
</div>
</td>
<td className="px-4 py-2 font-semibold">{jackpot?.drawsAt}</td>
<td className="px-4 py-2 font-semibold">{luckyNumber}</td>
<td className="px-4 py-2 font-semibold">
<button
disabled={participants.includes(luckyNumber)}
onClick={() => handlePurchase(i)}
className={`bg-black ${
participants.includes(luckyNumber)
? 'opacity-50 cursor-not-allowed'
: 'hover:bg-rose-600'
} text-white text-sm py-2 px-4 rounded-full`}
>
BUY NOW
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default DrawTime
view raw DrawTime.jsx hosted with ❤ by GitHub

Generator Component

The Generator Component

This component helps us to generate and send a specific number of strings to the smart contract. These generated numbers will then be put on display for users to buy as tickets for participating in the lottery. See the code snippet below.

import { useState } from 'react'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { FaTimes } from 'react-icons/fa'
import { exportLuckyNumbers } from '@/services/blockchain'
import { useSelector, useDispatch } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
const Generator = () => {
const router = useRouter()
const dispatch = useDispatch()
const { jackpotId } = router.query
const { setGeneratorModal } = globalActions
const [luckyNumbers, setLuckyNumbers] = useState('')
const { generatorModal } = useSelector((state) => state.globalState)
const handleSubmit = async (e) => {
e.preventDefault()
await toast.promise(
new Promise(async (resolve, reject) => {
await exportLuckyNumbers(jackpotId, generateLuckyNumbers(luckyNumbers))
.then(async () => {
setLuckyNumbers('')
dispatch(setGeneratorModal('scale-0'))
resolve()
})
.catch(() => reject())
}),
{
pending: 'Approve transaction...',
success: 'Lucky numbers saved to chain 👌',
error: 'Encountered error 🤯',
}
)
}
const generateLuckyNumbers = (count) => {
const result = []
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
for (let i = 0; i < count; i++) {
let string = ''
for (let j = 0; j < 6; j++) {
string += characters.charAt(Math.floor(Math.random() * charactersLength))
}
result.push(string)
}
return result
}
return (
<div
className={`fixed top-0 left-0 w-screen h-screen flex
items-center justify-center bg-black bg-opacity-50
transform transition-transform duration-300 ${generatorModal}`}
>
<div
className="bg-white shadow-xl shadow-[#0c2856] rounded-xl
w-11/12 md:w-2/5 h-7/12 p-6"
>
<form onSubmit={handleSubmit} className="flex flex-col">
<div className="flex justify-between items-center">
<p className="font-semibold">Generate Numbers</p>
<button
onClick={() => dispatch(setGeneratorModal('scale-0'))}
type="button"
className="border-0 bg-transparent focus:outline-none"
>
<FaTimes />
</button>
</div>
<div
className="flex justify-between items-center
bg-gray-300 rounded-xl p-2.5 my-5"
>
<input
className="block w-full bg-transparent
border-0 text-sm text-slate-500 focus:outline-none
focus:ring-0"
type="number"
step={1}
min={1}
name="luckyNumbers"
placeholder="Lucky Numbers e.g 19"
onChange={(e) => setLuckyNumbers(e.target.value)}
value={luckyNumbers}
/>
</div>
<button
type="submit"
className="flex flex-row justify-center items-center
w-full text-white text-md py-2 px-5 rounded-full
drop-shadow-xl bg-[#0c2856] hover:bg-[#1a396c]"
>
Generate and Save
</button>
</form>
</div>
</div>
)
}
export default Generator
view raw Generator.jsx hosted with ❤ by GitHub

Auth Chat Component

The Chat Authentication Component

This component authenticates users before they can chat with our platform. The CometChat SDK is used here under the hood to perform an authentication with the connected user's wallet. See the code below.

import { toast } from 'react-toastify'
import { FaTimes } from 'react-icons/fa'
import { useDispatch, useSelector } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { signUpWithCometChat, loginWithCometChat } from '@/services/chat'
const AuthChat = () => {
const { authModal, wallet } = useSelector((state) => state.globalState)
const { setAuthModal, setCurrentUser } = globalActions
const dispatch = useDispatch()
const { CometChat } = window
const handleSignUp = async () => {
if (!wallet) return toast.warning('Connect your wallet')
await toast.promise(
new Promise(async (resolve, reject) => {
await signUpWithCometChat(CometChat, wallet)
.then((user) => {
dispatch(setCurrentUser(JSON.parse(JSON.stringify(user))))
resolve()
})
.catch(() => reject())
}),
{
pending: 'Signing up...',
success: 'Account created, please login 👌',
error: 'Encountered error 🤯',
}
)
}
const handleLogin = async () => {
if (!wallet) return toast.warning('Connect your wallet')
await toast.promise(
new Promise(async (resolve, reject) => {
await loginWithCometChat(CometChat, wallet)
.then(async (user) => {
dispatch(setCurrentUser(JSON.parse(JSON.stringify(user))))
dispatch(setAuthModal('scale-0'))
resolve()
window.location.reload()
})
.catch(() => reject())
}),
{
pending: 'Logging in...',
success: 'login successfull 👌',
error: 'Encountered error 🤯',
}
)
}
return (
<div
className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center
bg-black bg-opacity-50 transform z-50 transition-transform duration-300 ${authModal}`}
>
<div className="bg-white shadow-lg shadow-slate-900 rounded-xl w-11/12 md:w-2/5 p-6 relative">
<div className="flex items-center justify-between">
<h2>Auth</h2>
<FaTimes className="cursor-pointer" onClick={() => dispatch(setAuthModal('scale-0'))} />
</div>
<div className="flex items-center justify-center space-x-4">
<button
className="p-2 bg-amber-600 rounded-md text-white focus:outline-none focus:ring-0"
onClick={handleLogin}
>
Login
</button>
<button
className="p-2 bg-gray-600 rounded-md text-white focus:outline-none focus:ring-0"
onClick={handleSignUp}
>
Sign up
</button>
</div>
</div>
</div>
)
}
export default AuthChat
view raw AuthChat.jsx hosted with ❤ by GitHub

Chat Component

The Chat Interface

This component utilizes the CometChat SDK to perform anonymous one-to-many chat among all authenticated users who have also joined the group. Here is the code for its implementation.

import { FaTimes } from 'react-icons/fa'
import Identicon from 'react-identicons'
import { useLayoutEffect, useState, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { sendMessage, getMessages, listenForMessage } from '@/services/chat'
const Chat = ({ id }) => {
const { chatModal, wallet } = useSelector((state) => state.globalState)
const { setChatModal } = globalActions
const dispatch = useDispatch()
const { CometChat } = window
const [message, setMessage] = useState('')
const [messages, setMessages] = useState([])
const messagesEndRef = useRef(null)
useLayoutEffect(() => {
setTimeout(async () => {
getMessages(CometChat, `guid_${id}`).then((msgs) => {
setMessages(msgs)
scrollToEnd()
})
listenForMessage(CometChat, `guid_${id}`).then((msg) => {
setMessages((prevMessages) => [...prevMessages, msg])
scrollToEnd()
})
}, 500)
}, [])
const onSendMessage = async (e) => {
e.preventDefault()
if (!message) return
new Promise(async (resolve, reject) => {
await sendMessage(CometChat, `guid_${id}`, message)
.then((msg) => {
setMessages((prevMsgs) => [...prevMsgs, msg])
setMessage('')
resolve(msg)
scrollToEnd()
})
.catch(() => reject())
})
}
const scrollToEnd = () => {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
}
return (
<div
className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center
bg-black bg-opacity-50 transform z-50 transition-transform duration-300 ${chatModal}`}
>
<div className="bg-white shadow-lg shadow-slate-900 rounded-xl w-11/12 md:w-4/5 h-[30rem] p-6 relative">
<div className="flex items-center justify-between">
<h2>Chat</h2>
<FaTimes className="cursor-pointer" onClick={() => dispatch(setChatModal('scale-0'))} />
</div>
<div className="flex flex-col overflow-y-scroll overflow-x-hidden h-[22rem] pb-5">
{messages?.map((msg, i) => (
<Message
key={i}
msg={msg.text}
time={Number(msg.sentAt + '000')}
uid={msg.sender.uid}
isCurrentUser={msg.sender.uid != wallet.toLowerCase()}
/>
))}
<div className="bg-transparent py-10" ref={messagesEndRef} />
</div>
<form onSubmit={onSendMessage} className="h-18 w-full mt-3">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="h-full w-full py-5 px-3 focus:outline-none focus:ring-0 rounded-md
border-none bg-slate-800 text-white placeholder-white"
placeholder="Leave a message..."
/>
</form>
</div>
</div>
)
}
export default Chat
const Message = ({ msg, isCurrentUser, time, uid }) => {
return isCurrentUser ? (
<div className="flex flex-col items-start mt-4">
<div className="flex justify-start items-start space-x-2">
<Identicon string={uid} size={30} className="rounded-full shadow-md" />
<div className="bg-gray-200 py-2 px-4 rounded-lg max-w-xs">
<p className="text-black ">{msg}</p>
</div>
</div>
<span className="text-gray-500 text-sm mt-2">{new Date(time).toLocaleString()}</span>
</div>
) : (
<div className="flex flex-col items-end mt-4">
<div className="flex justify-start items-start space-x-2">
<Identicon string={uid} size={30} className="rounded-full shadow-md" />
<div className="bg-amber-500 py-2 px-4 rounded-lg max-w-xs">
<p className="text-white">{msg}</p>
</div>
</div>
<span className="text-gray-500 text-sm mt-2">{new Date(time).toLocaleString()}</span>
</div>
)
}
view raw Chat.jsx hosted with ❤ by GitHub

Winners Component

The Winners Component

This component is activated when the perform draw button is clicked. It allows you to enter the number of winners you want. See the snippet below.

import { useState } from 'react'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { FaTimes } from 'react-icons/fa'
import { performDraw } from '@/services/blockchain'
import { useSelector, useDispatch } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
const Winners = () => {
const router = useRouter()
const dispatch = useDispatch()
const { resultId } = router.query
const { setWinnerModal } = globalActions
const [numberOfwinner, setNumberOfwinner] = useState('')
const { winnerModal } = useSelector((state) => state.globalState)
const handleSubmit = async (e) => {
e.preventDefault()
await toast.promise(
new Promise(async (resolve, reject) => {
await performDraw(resultId, numberOfwinner)
.then(async () => {
setNumberOfwinner('')
dispatch(setWinnerModal('scale-0'))
resolve()
})
.catch(() => reject())
}),
{
pending: 'Approve transaction...',
success: 'Draw performed successfully 👌',
error: 'Encountered error 🤯',
}
)
}
return (
<div
className={`fixed top-0 left-0 w-screen h-screen flex
items-center justify-center bg-black bg-opacity-50
transform transition-transform duration-300 ${winnerModal}`}
>
<div
className="bg-white shadow-xl shadow-[#0c2856] rounded-xl
w-11/12 md:w-2/5 h-7/12 p-6"
>
<form onSubmit={handleSubmit} className="flex flex-col">
<div className="flex justify-between items-center">
<p className="font-semibold">Emerging Winners</p>
<button
onClick={() => dispatch(setWinnerModal('scale-0'))}
type="button"
className="border-0 bg-transparent focus:outline-none"
>
<FaTimes />
</button>
</div>
<div
className="flex justify-between items-center
bg-gray-300 rounded-xl p-2.5 my-5"
>
<input
className="block w-full bg-transparent
border-0 text-sm text-slate-500 focus:outline-none
focus:ring-0"
type="number"
step={1}
min={1}
name="numberOfwinner"
placeholder="Lucky Numbers e.g 19"
onChange={(e) => setNumberOfwinner(e.target.value)}
value={numberOfwinner}
/>
</div>
<button
type="submit"
className="flex flex-row justify-center items-center
w-full text-white text-md py-2 px-5 rounded-full
drop-shadow-xl bg-[#0c2856] hover:bg-[#1a396c]"
>
Draw Now
</button>
</form>
</div>
</div>
)
}
export default Winners
view raw Winners.jsx hosted with ❤ by GitHub

Result Component

The Result Component

Almost as similar as the Draw time component, this component displays some statistics about the just concluded lottery, the winners and the losers, what the winners took home, and what the losers lost. The component also includes a button to perform the draw, which is only enabled once the countdown is at zero. See the coded implementation below.

import Link from 'next/link'
import Identicon from 'react-identicons'
import { useSelector, useDispatch } from 'react-redux'
import { FaEthereum } from 'react-icons/fa'
import Countdown from '@/components/Countdown'
import { truncate } from '@/services/blockchain'
import { globalActions } from '@/store/global_reducer'
const Result = ({ jackpot, participants, result }) => {
const { wallet } = useSelector((state) => state.globalState)
const { setWinnerModal } = globalActions
const dispatch = useDispatch()
return (
<div className="mx-auto py-16 bg-slate-100 space-y-2">
<div className="flex flex-col items-center justify-center text-center py-10">
<h1 className="text-2xl font-bold pb-4">Lottery Result</h1>
<p className="text-lg text-gray-600 font-semibold capitalize">{jackpot?.title}</p>
<p className="text-sm text-gray-500 w-full sm:w-2/3">{jackpot?.description}</p>
<p className="text-sm text-gray-500 w-full sm:w-2/3">
Result for{' '}
<span className="font-medium text-green-600">{result?.winners.length} winners</span> out
of <span className="font-medium text-black">{jackpot?.participants} participants</span>{' '}
<span className="font-medium text-gray-600">
{result?.winners.length > 0 ? 'Drawn' : 'Not Drawn'}
</span>
</p>
</div>
<div className="flex flex-col justify-center items-center space-y-4">
{jackpot?.expiresAt ? <Countdown timestamp={jackpot?.expiresAt} /> : null}
<div className="flex justify-center items-center space-x-2">
{wallet.toLowerCase() == jackpot?.owner ? (
<button
disabled={jackpot?.expiresAt > Date.now()}
onClick={() => dispatch(setWinnerModal('scale-100'))}
className={`flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
hover:bg-rose-600 font-semibold
${
jackpot?.expiresAt > Date.now()
? 'opacity-50 cursor-not-allowed'
: 'hover:bg-rose-600'
}`}
>
Perform Draw
</button>
) : null}
<Link
href={`/jackpots/` + jackpot?.id}
className="flex flex-nowrap border py-2 px-4 rounded-full bg-[#0c2856]
hover:bg-[#1a396c] cursor-pointer font-semibold text-white"
>
Jackpot
</Link>
</div>
</div>
<div className="flex flex-col-reverse sm:flex-row ">
<div
className="bg-white flex flex-col w-full sm:w-3/4 mx-auto
p-5 rounded-md"
>
<h4 className="text-2xl font-bold text-slate-700 text-center">Winners & Lossers</h4>
<div className="space-y-2 max-h-80 overflow-y-auto">
{participants?.map((participant, i) => (
<div
key={i}
className="flex justify-start items-center border-b border-gray-100 py-2 space-x-2"
>
<Identicon size={30} string={i} className="rounded-full h-12 w-12" />
<div className="flex justify-center items-center space-x-2 text-sm">
<p className="font-semibold text-lg text-slate-500">
{truncate(participant.account, 4, 4, 11)}
</p>
<p className="text-slate-500">{participant.lotteryNumber}</p>
{result?.winners.includes(participant.lotteryNumber) ? (
<p className="text-green-500 flex justify-start items-center">
+ <FaEthereum /> {result?.sharePerWinner} {' winner'}
</p>
) : (
<p className="text-red-500 flex justify-start items-center">
- <FaEthereum /> {jackpot?.ticketPrice}
</p>
)}
</div>
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default Result
view raw Result.jsx hosted with ❤ by GitHub

CometChatNoSSR Component

This is a special component created to help us load the CometChat module to the browsers window since NextJs is a server side rendering framework. See the code below.

import { initCometChat, checkAuthState } from '@/services/chat'
import { useEffect } from 'react'
import { globalActions } from '@/store/global_reducer'
import { useDispatch } from 'react-redux'
import { isWallectConnected } from '@/services/blockchain'
const CometChatNoSSR = () => {
window.CometChat = require('@cometchat-pro/chat').CometChat
const { CometChat } = window
const { setCurrentUser } = globalActions
const dispatch = useDispatch()
useEffect(() => {
initCometChat(CometChat).then(() => {
checkAuthState(CometChat).then((user) => {
dispatch(setCurrentUser(JSON.parse(JSON.stringify(user))))
})
})
isWallectConnected(CometChat)
}, [])
return null
}
export default CometChatNoSSR

Here is a free full video tutorial that can watch to help you learn how to build a decentralized NFT minting platform on my YouTube channel.

The Pages Components

In this section, let’s go through all the codes that makes for each one of the pages in this project. Please take not that these various pages must be created in the pages folder in the root directory of your project.

Home Page

The Home Page Component

This page contains the Header and Jackpots components, take a look at its simple implementation below. It uses a SSR (Server Side Rendering) technique to retrieve all lotteries from the blockchain without requiring a user to connect their wallet or be on a specifc chain.

import Head from 'next/head'
import Header from '../components/Header'
import Jackpots from '../components/Jackpots'
import { getLotteries } from '@/services/blockchain.srr'
export default function Home({ jackpots }) {
return (
<div>
<Head>
<title>Dapp Lottery</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen bg-slate-100">
<Header />
<Jackpots jackpots={jackpots} />
</div>
</div>
)
}
export const getServerSideProps = async () => {
const data = await getLotteries()
return {
props: { jackpots: JSON.parse(JSON.stringify(data)) },
}
}
view raw Home.jsx hosted with ❤ by GitHub

Create Lottery Page

Create Lottery Page

This page enables a user create to a lottery, of course, it collects information about the lottery such as the lottery title, description, image url, prize to be won, ticket cost, and the expiration data for the lottery. It is important to note that for creating a lottery, a user’s wallet must be connected and and the right chain/network. See the code below.

import Head from 'next/head'
import { useState } from 'react'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { useSelector } from 'react-redux'
import SubHeader from '../components/SubHeader'
import { createJackpot } from '@/services/blockchain'
export default function Create() {
const { wallet } = useSelector((state) => state.globalState)
const router = useRouter()
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [imageUrl, setImageUrl] = useState('')
const [prize, setPrize] = useState('')
const [ticketPrice, setTicketPrice] = useState('')
const [expiresAt, setExpiresAt] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
if (!wallet) return toast.warning('Wallet not connected')
if (!title || !description || !imageUrl || !prize || !ticketPrice || !expiresAt) return
const params = {
title,
description,
imageUrl,
prize,
ticketPrice,
expiresAt: new Date(expiresAt).getTime(),
}
await toast.promise(
new Promise(async (resolve, reject) => {
await createJackpot(params)
.then(async () => {
onReset()
router.push('/')
resolve()
})
.catch(() => reject())
}),
{
pending: 'Approve transaction...',
success: 'Jackpot created successfully 👌',
error: 'Encountered error 🤯',
}
)
}
const onReset = () => {
setTitle('')
setDescription('')
setImageUrl('')
setPrize('')
setTicketPrice('')
setExpiresAt('')
}
return (
<div>
<Head>
<title>Dapp Lottery - Create New Jackpot</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen bg-slate-100">
<SubHeader />
<div className="flex flex-col justify-center items-center mt-20">
<div className=" flex flex-col items-center justify-center my-5">
<h1 className="text-2xl font-bold text-slate-800 py-5">Create Jackpots</h1>
<p className="text-center text-sm text-slate-600">
We bring a persolan and effective every project we work on. <br />
which is why our client love why they keep coming back.
</p>
</div>
<form onSubmit={handleSubmit} className="w-full max-w-md">
<div className="mb-4">
<input
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="title"
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>
<div className="mb-4">
<input
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="imageUrl"
type="url"
placeholder="Image URL"
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
required
/>
</div>
<div className="mb-4">
<input
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="prize"
type="number"
step={0.01}
min={0.01}
placeholder="Prize"
value={prize}
onChange={(e) => setPrize(e.target.value)}
required
/>
</div>
<div className="mb-6">
<input
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="ticketPrice"
type="number"
step={0.01}
min={0.01}
placeholder="Ticket price"
value={ticketPrice}
onChange={(e) => setTicketPrice(e.target.value)}
required
/>
</div>
<div className="mb-6">
<input
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="expiresAt"
type="datetime-local"
value={expiresAt}
onChange={(e) => setExpiresAt(e.target.value)}
required
/>
</div>
<div className="mb-4">
<textarea
className="appearance-none border rounded w-full py-2 px-3
text-gray-700 leading-tight focus:outline-none
focus:shadow-outline"
id="description"
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
required
></textarea>
</div>
<div className="flex justify-center">
<button
className="w-full bg-[#0c2856] hover:bg-[#1a396c] text-white font-bold
py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Submit Jackpot
</button>
</div>
</form>
</div>
</div>
</div>
)
}
view raw Create.jsx hosted with ❤ by GitHub

The Jackpots Page

The Jackpots Page

Listen up, the way you create this page is quite different as to how the other pages have been created since its a dynamic component.

First, create a folder called jackpots inside the pages directory. Next, create a file called [jackpotId].jsx exactly in this format just created and paste the codes below inside of it. See the codes below.

import Head from 'next/head'
import { useEffect, useState } from 'react'
import AuthChat from '@/components/AuthChat'
import DrawTime from '@/components/DrawTime'
import SubHeader from '@/components/SubHeader'
import Generator from '@/components/Generator'
import { useSelector, useDispatch } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { getLottery, getLuckyNumbers, getPurchasedNumbers } from '@/services/blockchain.srr'
import { getGroup, getMessages } from '@/services/chat'
import Chat from '@/components/Chat'
export default function Draws({ lottery, lotteryNumbers, numbersPurchased }) {
const { luckyNumbers, purchasedNumbers, jackpot, wallet } = useSelector(
(state) => state.globalState
)
const { setLuckyNumbers, setPurchasedNumbers, setJackpot, setGroup } = globalActions
const dispatch = useDispatch()
const { CometChat } = window
useEffect(() => {
dispatch(setJackpot(lottery))
dispatch(setLuckyNumbers(lotteryNumbers))
dispatch(setPurchasedNumbers(numbersPurchased))
setTimeout(async () => {
const groupData = await getGroup(CometChat, `guid_${lottery?.id}`)
if (groupData) dispatch(setGroup(JSON.parse(JSON.stringify(groupData))))
}, 500)
}, [])
return (
<div className="min-h-screen">
<Head>
<title>Dapp Lottery | Draws</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen bg-slate-100">
<SubHeader />
<DrawTime jackpot={jackpot} luckyNumbers={luckyNumbers} participants={purchasedNumbers} />
<Generator />
<AuthChat />
<Chat id={lottery?.id} />
</div>
</div>
)
}
export const getServerSideProps = async (context) => {
const { jackpotId } = context.query
const lottery = await getLottery(jackpotId)
const purchasedNumbers = await getPurchasedNumbers(jackpotId)
const lotteryNumbers = await getLuckyNumbers(jackpotId)
return {
props: {
lottery: JSON.parse(JSON.stringify(lottery)),
lotteryNumbers: JSON.parse(JSON.stringify(lotteryNumbers)),
numbersPurchased: JSON.parse(JSON.stringify(purchasedNumbers)),
},
}
}
view raw [jackpotId].jsx hosted with ❤ by GitHub

Again, this page as the Home page utilizes the NextJs server side rendering technique to retrieive the lottery information from the chain without requiring users to login with their wallet address.

The Results Page

The Results Page

This page like the Jackpots page uses the NextJs dynamic routing technique to see the result for each one of the lottery. Head to the pages directory and create a folder called jackpots , inside of this new folder create a file in this format called [resultId].jsx and past the codes below inside and save.

import Head from 'next/head'
import { useEffect } from 'react'
import Result from '@/components/Result'
import Winners from '@/components/Winners'
import SubHeader from '@/components/SubHeader'
import { useDispatch, useSelector } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { getLottery, getLotteryResult, getParticipants } from '@/services/blockchain.srr'
export default function Results({ lottery, participantList, lotteryResult }) {
const { participants, jackpot, result } = useSelector((state) => state.globalState)
const { setParticipants, setJackpot, setResult } = globalActions
const dispatch = useDispatch()
useEffect(() => {
dispatch(setResult(lotteryResult))
dispatch(setJackpot(lottery))
dispatch(setParticipants(participantList))
}, [])
return (
<div>
<Head>
<title>Dapp Lottery | Results</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen bg-slate-100">
<SubHeader />
<Result jackpot={jackpot} participants={participants} result={result} />
<Winners />
</div>
</div>
)
}
export const getServerSideProps = async (context) => {
const { resultId } = context.query
const lottery = await getLottery(resultId)
const participantList = await getParticipants(resultId)
const lotteryResult = await getLotteryResult(resultId)
return {
props: {
lottery: JSON.parse(JSON.stringify(lottery)),
participantList: JSON.parse(JSON.stringify(participantList)),
lotteryResult: JSON.parse(JSON.stringify(lotteryResult)),
},
}
}
view raw [resultId].jsx hosted with ❤ by GitHub

The _app.tsx file

This is an entry file that comes pre-configured with NextJs which you will find within the pages folder of your project. Open it and replace its codes with the one below.

import '@/styles/global.css'
import { store } from '../store'
import { AppProps } from 'next/app'
import { Provider } from 'react-redux'
import { useEffect, useState } from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { ToastContainer } from 'react-toastify'
import CometChatSSR from '@/components/CometChatNoSSR'
export default function MyApp({ Component, pageProps }: AppProps) {
const [showChild, setShowChild] = useState(false)
useEffect(() => {
setShowChild(true)
}, [])
if (!showChild || typeof window === 'undefined') {
return null
} else {
return (
<Provider store={store}>
<CometChatSSR />
<Component {...pageProps} />
<ToastContainer
position="bottom-center"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="dark"
/>
</Provider>
)
}
}
view raw _app.tsx hosted with ❤ by GitHub

State Management Files

Now, it must be brought to your notice that this project uses the redux-toolkit package to keep the shared data used across this application in a central location. Follow the steps below to replicate. Before proceeding to the step below, create a folder at the root of this project called store and create the following file within it.

Redux States
This file will help us keep together all the states of the variables we are using in this application together. Within this store directory create another folder called states and inside of it create a file called global_states.js and paste the codes below inside and save.

export const global_states = {
wallet: '',
generatorModal: 'scale-0',
winnerModal: 'scale-0',
authModal: 'scale-0',
chatModal: 'scale-0',
jackpots: [],
jackpot: null,
result: null,
group: null,
currentUser: null,
luckyNumbers: [],
participants: [],
purchasedNumbers: [],
}

Redux Actions
Next, create another folder in the store directory called actions and inside of it create a file called global_actions.js and paste the codes below inside and save.

export const global_actions = {
updateWallet: (state, action) => {
state.wallet = action.payload
},
setGeneratorModal: (state, action) => {
state.generatorModal = action.payload
},
setWinnerModal: (state, action) => {
state.winnerModal = action.payload
},
setChatModal: (state, action) => {
state.chatModal = action.payload
},
setAuthModal: (state, action) => {
state.authModal = action.payload
},
setCurrentUser: (state, action) => {
state.currentUser = action.payload
},
setJackpots: (state, action) => {
state.jackpots = action.payload
},
setJackpot: (state, action) => {
state.jackpot = action.payload
},
setLuckyNumbers: (state, action) => {
state.luckyNumbers = action.payload
},
setPurchasedNumbers: (state, action) => {
state.purchasedNumbers = action.payload
},
setParticipants: (state, action) => {
state.participants = action.payload
},
setResult: (state, action) => {
state.result = action.payload
},
setGroup: (state, action) => {
state.group = action.payload
},
}

Redux Reducer
Let’s create a ruducer or a redux slice that will help us manage everything that has to do with out global states and actions recently created. Within the store folder, create a file named global_reducer.js and save. See the codes below.

import { createSlice } from '@reduxjs/toolkit'
import { global_actions } from './actions/global_actions'
import { global_states } from './states/global_states'
export const globalSlice = createSlice({
name: 'global',
initialState: global_states,
reducers: global_actions,
})
export const globalActions = globalSlice.actions
export default globalSlice.reducer

Lastly, let’s bundle up and help us manage all the reducers/slices in our store. Within this store folder create another file named index.js, paste the codes below inside of it and save.

import { configureStore } from '@reduxjs/toolkit'
import global_reducer from './global_reducer'
export const store = configureStore({
reducer: {
globalState: global_reducer,
},
})
view raw index.js hosted with ❤ by GitHub

We can include as many reducers as possible here in this store/index file.

Services

We have three services used here in this application which you will create in a folder called services in the root of this project.

The blockchain, blockchain.ssr, and the chat services. The blockchain services deals with all functions that sends information to our smart contract, while the blockchain ssr file reads data stored in our smart contract. This is extremely important and it ssr file ensures that we can retrieve data from the blockchain without needing to first connect our wallet to Metamask.

We also have a chat service which helps us communicate with the CometChat SDK. See the codes below and be sure to create each one of these files in the services folder.

import abi from '@/artifacts/contracts/DappLottery.sol/DappLottery.json'
import address from '@/artifacts/contractAddress.json'
import { globalActions } from '@/store/global_reducer'
import { store } from '@/store'
import {
getLottery,
getLotteryResult,
getLuckyNumbers,
getParticipants,
getPurchasedNumbers,
} from '@/services/blockchain.srr'
import { ethers } from 'ethers'
import { logOutWithCometChat } from './chat'
const {
updateWallet,
setLuckyNumbers,
setParticipants,
setPurchasedNumbers,
setJackpot,
setResult,
setCurrentUser,
} = globalActions
const contractAddress = address.address
const contractAbi = abi.abi
let tx, ethereum
if (typeof window !== 'undefined') {
ethereum = window.ethereum
}
const toWei = (num) => ethers.utils.parseEther(num.toString())
const getEthereumContract = async () => {
const provider = new ethers.providers.Web3Provider(ethereum)
const signer = provider.getSigner()
const contract = new ethers.Contract(contractAddress, contractAbi, signer)
return contract
}
const isWallectConnected = async (CometChat) => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_accounts' })
window.ethereum.on('chainChanged', (chainId) => {
window.location.reload()
})
window.ethereum.on('accountsChanged', async () => {
store.dispatch(updateWallet(accounts[0]))
store.dispatch(setCurrentUser(null))
logOutWithCometChat(CometChat).then(() => console.log('Logged out'))
await isWallectConnected(CometChat)
})
if (accounts.length) {
store.dispatch(updateWallet(accounts[0]))
} else {
store.dispatch(updateWallet(''))
notifyUser('Please connect wallet.')
console.log('No accounts found.')
}
} catch (error) {
reportError(error)
}
}
const connectWallet = async () => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
store.dispatch(updateWallet(accounts[0]))
} catch (error) {
reportError(error)
}
}
const createJackpot = async ({ title, description, imageUrl, prize, ticketPrice, expiresAt }) => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const connectedAccount = store.getState().globalState.wallet
const contract = await getEthereumContract()
tx = await contract.createLottery(
title,
description,
imageUrl,
toWei(prize),
toWei(ticketPrice),
expiresAt,
{
from: connectedAccount,
}
)
await tx.wait()
} catch (error) {
reportError(error)
}
}
const buyTicket = async (id, luckyNumberId, ticketPrice) => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const connectedAccount = store.getState().globalState.wallet
const contract = await getEthereumContract()
tx = await contract.buyTicket(id, luckyNumberId, {
from: connectedAccount,
value: toWei(ticketPrice),
})
await tx.wait()
const purchasedNumbers = await getPurchasedNumbers(id)
const lotteryParticipants = await getParticipants(id)
const lottery = await getLottery(id)
store.dispatch(setPurchasedNumbers(purchasedNumbers))
store.dispatch(setParticipants(lotteryParticipants))
store.dispatch(setJackpot(lottery))
} catch (error) {
reportError(error)
}
}
const performDraw = async (id, numOfWinners) => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const connectedAccount = store.getState().globalState.wallet
const contract = await getEthereumContract()
tx = await contract.randomlySelectWinners(id, numOfWinners, {
from: connectedAccount,
})
await tx.wait()
const lotteryParticipants = await getParticipants(id)
const lottery = await getLottery(id)
const result = await getLotteryResult(id)
store.dispatch(setParticipants(lotteryParticipants))
store.dispatch(setJackpot(lottery))
store.dispatch(setResult(result))
} catch (error) {
reportError(error)
}
}
const exportLuckyNumbers = async (id, luckyNumbers) => {
try {
if (!ethereum) return notifyUser('Please install Metamask')
const connectedAccount = store.getState().globalState.wallet
const contract = await getEthereumContract()
tx = await contract.importLuckyNumbers(id, luckyNumbers, {
from: connectedAccount,
})
await tx.wait()
const lotteryNumbers = await getLuckyNumbers(id)
store.dispatch(setLuckyNumbers(lotteryNumbers))
} catch (error) {
reportError(error)
}
}
const reportError = (error) => {
console.log(error.message)
}
const notifyUser = (msg) => {
console.log(msg)
}
const truncate = (text, startChars, endChars, maxLength) => {
if (text.length > maxLength) {
let start = text.substring(0, startChars)
let end = text.substring(text.length - endChars, text.length)
while (start.length + end.length < maxLength) {
start = start + '.'
}
return start + end
}
return text
}
export {
isWallectConnected,
connectWallet,
createJackpot,
exportLuckyNumbers,
buyTicket,
performDraw,
truncate,
}
view raw blockchain.js hosted with ❤ by GitHub
const abi = require('../artifacts/contracts/DappLottery.sol/DappLottery.json')
const address = require('../artifacts/contractAddress.json')
const { ethers } = require('ethers')
const contractAddress = address.address
const contractAbi = abi.abi
const fromWei = (num) => ethers.utils.formatEther(num)
const getEtheriumContract = async () => {
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545')
const wallet = ethers.Wallet.createRandom()
// Set the new account as the signer for the provider
const signer = provider.getSigner(wallet.address)
const contract = new ethers.Contract(contractAddress, contractAbi, signer)
return contract
}
const getLotteries = async () => {
const lotteries = await (await getEtheriumContract()).functions.getLotteries()
return structureLotteries(lotteries[0])
}
const getLottery = async (id) => {
const lottery = await (await getEtheriumContract()).functions.getLottery(id)
return structureLotteries([lottery[0]])[0]
}
const getLuckyNumbers = async (id) => {
const luckyNumbers = await (await getEtheriumContract()).functions.getLotteryLuckyNumbers(id)
return luckyNumbers[0]
}
const getLotteryResult = async (id) => {
const lotterResult = await (await getEtheriumContract()).functions.getLotteryResult(id)
return structuredResult(lotterResult[0])
}
const getParticipants = async (id) => {
const participants = await (await getEtheriumContract()).functions.getLotteryParticipants(id)
return structuredParticipants(participants[0])
}
const getPurchasedNumbers = async (id) => {
const participants = await (await getEtheriumContract()).functions.getLotteryParticipants(id)
return structuredNumbers(participants[0])
}
function formatDate(timestamp) {
const date = new Date(timestamp)
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const monthsOfYear = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
]
const dayOfWeek = daysOfWeek[date.getDay()]
const monthOfYear = monthsOfYear[date.getMonth()]
const dayOfMonth = date.getDate()
const year = date.getFullYear()
return `${dayOfWeek} ${monthOfYear} ${dayOfMonth}, ${year}`
}
const structureLotteries = (lotteries) =>
lotteries.map((lottery) => ({
id: Number(lottery.id),
title: lottery.title,
description: lottery.description,
owner: lottery.owner.toLowerCase(),
prize: fromWei(lottery.prize),
ticketPrice: fromWei(lottery.ticketPrice),
image: lottery.image,
createdAt: formatDate(Number(lottery.createdAt + '000')),
drawsAt: formatDate(Number(lottery.expiresAt)),
expiresAt: Number(lottery.expiresAt),
participants: Number(lottery.participants),
drawn: lottery.drawn,
}))
const structuredParticipants = (participants) =>
participants.map((participant) => ({
account: participant[0].toLowerCase(),
lotteryNumber: participant[1],
paid: participant[2],
}))
const structuredNumbers = (participants) => {
const purchasedNumbers = []
for (let i = 0; i < participants.length; i++) {
const purchasedNumber = participants[i][1]
purchasedNumbers.push(purchasedNumber)
}
return purchasedNumbers
}
const structuredResult = (result) => {
const LotteryResult = {
id: Number(result[0]),
completed: result[1],
paidout: result[2],
timestamp: Number(result[3] + '000'),
sharePerWinner: fromWei(result[4]),
winners: [],
}
for (let i = 0; i < result[5].length; i++) {
const winner = result[5][i][1]
LotteryResult.winners.push(winner)
}
return LotteryResult
}
module.exports = {
getLotteries,
getLottery,
structureLotteries,
getLuckyNumbers,
getParticipants,
getPurchasedNumbers,
getLotteryResult,
}
const CONSTANTS = {
APP_ID: process.env.NEXT_PUBLIC_APP_ID,
REGION: process.env.NEXT_PUBLIC_REGION,
Auth_Key: process.env.NEXT_PUBLIC_AUTH_KEY,
}
const initCometChat = async (CometChat) => {
const appID = CONSTANTS.APP_ID
const region = CONSTANTS.REGION
const appSetting = new CometChat.AppSettingsBuilder()
.subscribePresenceForAllUsers()
.setRegion(region)
.build()
await CometChat.init(appID, appSetting)
.then(() => console.log('Initialization completed successfully'))
.catch((error) => console.log(error))
}
const loginWithCometChat = async (CometChat, UID) => {
const authKey = CONSTANTS.Auth_Key
return new Promise(async (resolve, reject) => {
await CometChat.login(UID, authKey)
.then((user) => resolve(user))
.catch((error) => reject(error))
})
}
const signUpWithCometChat = async (CometChat, UID) => {
const authKey = CONSTANTS.Auth_Key
const user = new CometChat.User(UID)
user.setName(UID)
return new Promise(async (resolve, reject) => {
await CometChat.createUser(user, authKey)
.then((user) => resolve(user))
.catch((error) => reject(error))
})
}
const logOutWithCometChat = async (CometChat) => {
return new Promise(async (resolve, reject) => {
await CometChat.logout()
.then(() => resolve())
.catch(() => reject())
})
}
const checkAuthState = async (CometChat) => {
return new Promise(async (resolve, reject) => {
await CometChat.getLoggedinUser()
.then((user) => resolve(user))
.catch((error) => reject(error))
})
}
const createNewGroup = async (CometChat, GUID, groupName) => {
const groupType = CometChat.GROUP_TYPE.PUBLIC
const password = ''
const group = new CometChat.Group(GUID, groupName, groupType, password)
return new Promise(async (resolve, reject) => {
await CometChat.createGroup(group)
.then((group) => resolve(group))
.catch((error) => reject(error))
})
}
const getGroup = async (CometChat, GUID) => {
return new Promise(async (resolve, reject) => {
await CometChat.getGroup(GUID)
.then((group) => resolve(group))
.catch((error) => reject(error))
})
}
const joinGroup = async (CometChat, GUID) => {
const groupType = CometChat.GROUP_TYPE.PUBLIC
const password = ''
return new Promise(async (resolve, reject) => {
await CometChat.joinGroup(GUID, groupType, password)
.then((group) => resolve(group))
.catch((error) => reject(error))
})
}
const getMessages = async (CometChat, GUID) => {
const limit = 30
const messagesRequest = new CometChat.MessagesRequestBuilder()
.setGUID(GUID)
.setLimit(limit)
.build()
return new Promise(async (resolve, reject) => {
await messagesRequest
.fetchPrevious()
.then((messages) => resolve(messages.filter((msg) => msg.type == 'text')))
.catch((error) => reject(error))
})
}
const sendMessage = async (CometChat, receiverID, messageText) => {
const receiverType = CometChat.RECEIVER_TYPE.GROUP
const textMessage = new CometChat.TextMessage(receiverID, messageText, receiverType)
return new Promise(async (resolve, reject) => {
await CometChat.sendMessage(textMessage)
.then((message) => resolve(message))
.catch((error) => reject(error))
})
}
const listenForMessage = async (CometChat, listenerID) => {
return new Promise(async (resolve, reject) => {
CometChat.addMessageListener(
listenerID,
new CometChat.MessageListener({
onTextMessageReceived: (message) => resolve(message),
})
)
})
}
export {
initCometChat,
loginWithCometChat,
signUpWithCometChat,
logOutWithCometChat,
checkAuthState,
createNewGroup,
getGroup,
getMessages,
joinGroup,
sendMessage,
listenForMessage,
}
view raw chat.js hosted with ❤ by GitHub

Fantastic, now let’s include the essential assets used in this project.

Static Assets

At the root off your application, create a folder called assets, download the images found in this location and store them in the assets directory.

Also, don’t forget to instruct NextJs to allow your application to load images from any location. At the root of your application, create a file named next.config.js and paste the code below in it and save.

// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
},
}
view raw next.config.js hosted with ❤ by GitHub

And there you have it, congratulations you have successfully created a web3 lottery app, you just need to run the following commands on your terminal to see it live on your browers.

yarn dev #terminal 2
Enter fullscreen mode Exit fullscreen mode

The two commands above will spin up your project online and can be visited on the browser on localhost:3000.

If you're confused about web3 development and want visual materials, get my Fullstack NFT Marketplace and Minting courses.

Fullstack NFT Minting (Course)

Take the first step towards becoming a highly sought-after smart contract developer by enrolling in my courses on NFTs Minting and Marketplace. Enroll now and let's embark on this exciting journey together!

Conclusion

To conclude, this technology presents an exciting opportunity to revolutionize the traditional gambling industry.

The use of blockchain technology ensures transparency, security, and immutability in the lottery system, while also eliminating the need for intermediaries. This tutorial on building a Lottery DApp with NextJs, Solidity, and CometChat is a valuable resource for developers who want to create cutting-edge decentralized applications.

By following the step-by-step guide, we have learned how to create a fair and transparent lottery system on the blockchain. So why not start building your own Lottery DApp today and disrupt the traditional gambling industry? Don't forget to subscribe to the YouTube channel.

See you next time!

About the Author

Gospel Darlington is a full-stack blockchain developer with 7+ years of experience in the software development industry.

By combining Software Development, writing, and teaching, he demonstrates how to build decentralized applications on EVM-compatible blockchain networks.

His stacks include JavaScript, React, Vue, Angular, Node, React Native, NextJs, Solidity, and more.

For more information about him, kindly visit and follow his page on Twitter, Github, LinkedIn, or his website.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.