WEB3DEV

Cover image for Como construir um DApp de Loteria Transparente e Seguro com NextJs, Solidity e CometChat
Paulo Gio
Paulo Gio

Posted on

Como construir um DApp de Loteria Transparente e Seguro com NextJs, Solidity e CometChat

O que você estará construindo: veja a demonstração ao vivo e o repositório git.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*ZtegG5jQ4sc2oJpcjEysbA.png

Dapp Lottery por Darlington Gospel

https://miro.medium.com/v2/resize:fit:1100/0*UwXf8WJdQ2_hyxje.gif

https://miro.medium.com/v2/resize:fit:1100/0*CRDE-O510vPIq0EV.gif

Introdução

Se você está procurando construir uma aplicação descentralizada de última geração que combina o poder da tecnologia blockchain, comunicação em tempo real e conteúdo gerado pelo usuário, então este tutorial sobre como construir um DApp de Loteria com NextJs, Solidity, e CometChat é para você.

Seja você um desenvolvedor experiente ou apenas começando, este guia passo a passo o levará através do processo de criação de um sistema de loteria justo e transparente na blockchain. Então, por que não começar a construir seu próprio DApp de Loteria hoje e perturbar a indústria tradicional de jogos de azar?

E se você está interessado em aprender mais sobre o desenvolvimento Web3, não se esqueça de se inscrever no meu canal do YouTube e conferir meu conteúdo premium Web3 e serviços.

Agora, vamos mergulhar neste tutorial.

Pré-requisitos

Você precisará das seguintes ferramentas instaladas para construir junto comigo:

  • Nodejs (Importante)
  • EthersJs
  • Hardhat
  • Redux Toolkit
  • Yarn
  • Metamask
  • NextJs
  • Tailwind CSS
  • SDK do CometChat

Para configurar sua Metamask para este projeto, eu recomendo que assista ao vídeo abaixo.

Instalando Dependências

Clone o kit inicial e abra-o no VS Code usando o comando abaixo:

git clone https://github.com/Daltonic/tailwind_ethers_starter_kit 

<NOME_DO_PROJETO>

cd <NOME_DO_PROJETO>
Enter fullscreen mode Exit fullscreen mode

Agora, execute yarn install no terminal para ter todas as dependências deste projeto instaladas.

Configurando o SDK do CometChat

Siga os passos abaixo para configurar o SDK do CometChat; no final, você deve salvar essas chaves como uma variável de ambiente.

PASSO 1:

Vá para o painel de controle do CometChat e crie uma conta.

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*Q35qdSUL4xBOeDHS.png

PASSO 2:

Após registrar-se, faça login no painel de controle do CometChat.

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*_aOH_RMzsLe-2Eh8.png

PASSO 3:

No painel de controle, adicione um novo aplicativo chamado “DappLottery”.

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*ixyY9d-cath5dk84.png

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*xiQf3cKNIfKhxvwh.png

PASSO 4:

Selecione o aplicativo que acabou de criar na lista.

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*l4C4NEKXL-Bw5H7v.png

PASSO 5:

Na página de início rápido (Quick Start), copie o APP ID, REGION, e AUTH KEY, para o seu arquivo .env.local. Veja a imagem e o trecho de código.

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*BzyPZSL-hngQXKE0.png

Substitua as chaves do REACT_COMET_CHAT pelos seus valores apropriados.

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

O arquivo .env.local deve ser criado na raiz do seu projeto.

Configurando o script Hardhat

Na raiz deste projeto, abra o arquivo hardhat.config.js e substitua seu conteúdo com as seguintes configurações.

O script acima instrui o Hardhat sobre duas regras importantes:

  1. Networks (Redes): Este bloco contém as configurações para a sua escolha de redes. Durante a implantação, o Hardhat irá requerer que você especifique uma rede para enviar seus contratos inteligentes.
  2. Solidity: Isso descreve a versão do compilador a ser usada pelo Hardhat para compilar seus códigos de contratos inteligentes em bytecodes e ABI.

Configurando o Script de Implantação

Navegue até a pasta scripts e, em seguida, para o seu arquivo deploy.js e cole o código abaixo nele. Se você não conseguir encontrar uma pasta scripts, crie uma, crie um arquivo deploy.js e cole o seguinte código nele:

Quando executado como um comando de implantação do Hardhat, o script acima implantará o seu contrato inteligente especificado na rede de sua escolha.

Se você está lutando com um computador de especificações inferiores, ou quer fazer alguns códigos web3 enquanto estiver em movimento, confira este vídeo para aprender como configurar corretamente um projeto web3 com o Gitpod.

O Arquivo de Contrato Inteligente

Agora que concluímos as configurações iniciais, vamos criar o contrato inteligente para este projeto. Crie uma nova pasta chamada contracts na raiz do seu projeto.

Crie um novo arquivo chamado DappLottery.sol dentro desta pasta de contratos; este arquivo conterá toda a lógica que rege o contrato inteligente.

Copie, cole e salve os seguintes códigos no arquivo DappLottery.sol. Veja o código completo abaixo.

//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"
        );

        // Inicialize um array para armazenar os vencedores selecionados
        ParticipantStruct[] memory winners = new ParticipantStruct[](numOfWinners);
        ParticipantStruct[] memory participants = lotteryParticipants[id];

        // Inicialize a lista de índices com os valores 0, 1, ..., n-1
        uint256[] memory indices = new uint256[](participants.length);
        for (uint256 i = 0; i < participants.length; i++) {
            indices[i] = i;
        }

        // Embaralhe a lista de índices usando o algoritmo de Fisher-Yates
        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;
        }

        // Selecione os vencedores usando os primeiros índices numOfWinners
        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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Eu tenho um livro para ajudar você a dominar a linguagem web3 (Solidity), pegue sua cópia aqui.

https://miro.medium.com/v2/resize:fit:933/0*rMgXhU-yYP1Cz3d4.png

Agora, vamos discutir alguns dos detalhes do que está acontecendo no contrato inteligente acima. Temos os seguintes itens:

Este é um contrato inteligente Solidity chamado DappLottery, que permite a criação de uma loteria onde os usuários podem comprar tíquetes e participar de uma chance de ganhar um prêmio. O contrato inteligente tem várias funções que executam diferentes tarefas:

  1. Ownable: Este é um contrato importado do OpenZeppelin que fornece um mecanismo básico de controle de acesso para restringir o acesso a certas funções apenas para o proprietário do contrato.
  2. Counters: Este é um contrato importado do OpenZeppelin que fornece uma maneira de manter o controle do número total de loterias criadas.
  3. LotteryStruct: Esta é uma struct que define as propriedades de uma loteria, como id, título (title), descrição (description), imagem (image), prêmio (prize), preço do bilhete (ticketPrice), participantes (participants), sorteados (drawn), proprietário (owner), data de criação (createdAt) e data de expiração (expiresAt).
  4. ParticipantStruct: Esta é uma struct que define as propriedades de um participante, como conta (account), número da loteria (lotteryNumber) e pago (paid).
  5. LotteryResultStruct: Esta é uma struct que define as propriedades de um resultado de loteria, como id, concluído (completed), pago, marca temporal (timestamp), parcela por vencedor (sharePerWinner), e um array de vencedores (winners), que são do tipo ParticipantStruct.
  6. servicePercent e serviceBalance: São variáveis de estado que representam a porcentagem de taxa de serviço cobrada por loteria e o saldo total ganho com taxas de serviço, respectivamente.
  7. lotteries, lotteryParticipants, lotteryLuckyNumbers, luckyNumberUsed e lotteryResult: Estas são correspondências utilizadas para armazenar e recuperar dados relacionados a loterias, seus participantes, números da sorte, resultados da loteria e se um número da sorte foi utilizado.

As seguintes são as funções fornecidas por este contrato inteligente:

  1. constructor(uint256 _servicePercent): Esta é a função construtora que inicializa a variável servicePercent com a porcentagem da taxa de serviço cobrada por loteria.
  2. createLottery(): Esta função permite a criação de uma nova loteria com um título, descrição e imagem, prêmio, preço do bilhete e data da expiração. Ela também verifica algumas condições antes de criar a loteria, como garantir que o título, descrição e imagem não estejam vazios, prêmio e preço do bilhete não são zero, e a data da expiração está no futuro.
  3. importLuckyNumbers(): Esta função permite que o proprietário de uma loteria importe uma lista de números da sorte (luckyNumbers) que serão utilizados para selecionar os vencedores da loteria. Ela verifica algumas condições antes de importar a lista, como garantir que os números da sorte não estão vazios e que a loteria não tem nenhum participante ainda.
  4. buyTicket(): Esta função permite aos usuários comprar tíquetes para uma loteria, especificando o id da loteria e o ID do número da sorte (luckyNumberId) que desejam usar. Ela verifica algumas condições antes de permitir a compra, como garantir que o número da sorte não tenha sido usado antes e que o usuário tenha fundos suficientes para comprar o bilhete.
  5. randomlySelectWinners(): Esta função seleciona os vencedores de uma loteria aleatoriamente a partir da lista de participantes usando o algoritmo Fisher-Yates. Ela verifica algumas condições antes de selecionar os vencedores, como garantir que a loteria não foi concluída e que o número de vencedores selecionados não excede o número de participantes.
  6. payLotteryWinners(): Esta é uma função interna que paga os vencedores de uma loteria, calculando a parcela do prêmio a que cada vencedor tem direito e, em seguida, transferindo a quantidade adequada de fundos para a conta de cada vencedor.

No geral, este contrato inteligente oferece uma maneira simples e segura de criar e gerenciar loterias na blockchain Ethereum. Ele garante transparência e equidade na seleção dos vencedores e automatiza o processo de pagamento para reduzir o risco de fraude ou erros.

A seguir, execute os comandos abaixo para implantar o contrato inteligente na rede.

yarn hardhat node # Terminal #1

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

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*uVwELN-YTkdxDRgm.png

Se você precisar de mais ajuda para configurar o Hardhat ou implantar seu DApp Fullstack, assista a este vídeo. Ele ensinará como fazer uma implantação Omini-chain.

Desenvolvendo o Frontend

Agora que temos nosso contrato inteligente na rede e todos os nossos artefatos (bytecodes e ABI) gerados, vamos preparar a interface do usuário com o React.

Componentes

No diretório raiz, crie uma nova pasta chamada components para abrigar todos os componentes NextJs deste projeto.

Para cada um dos componentes abaixo, você terá que criar seus respectivos arquivos na pasta de componentes.

Componentes Header e Sub-Header (Cabeçalho e Sub-cabeçalho)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*Yvm3R5u7akB92f7P.png

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*ExcmvJu-jvOlAv-e.png

Este componente contém o logotipo, elementos de navegação fictícios e um botão de conexão de carteira, veja o código abaixo.

Dentro da pasta components, crie dois arquivos, Header.jsx e SubHeader.jsx respectivamente, e cole os códigos acima neles.

Componente Jackpots

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*kYOryzQ8WbcnIX96.png

Este componente foi construído para exibir os cartões em visualização de grade, como pode ser visto na imagem acima. Veja os códigos abaixo para entender como recriar o componente.

Componente Countdown (Contagem Regressiva)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*2AF10ciWUgLwOMOq.png

Este componente aceita uma marca temporal e renderiza uma contagem regressiva que conta de dias a segundos. Veja os códigos abaixo.

Componente Draw Time (Hora do Sorteio)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*7SpI2GXWjpYn-mWQ.png

Este componente exibe os detalhes de uma loteria, alguns botões para gerar números de loteria, criar bate-papo em grupo, ver a página de resultado da loteria e fazer login na interface de bate-papo. Por fim, contém uma tabela para renderizar todos os números de loteria gerados. Veja os códigos abaixo.

Componente Generator (Gerador)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*remqK21YBiyDVOCc.png

Este componente nos ajuda a gerar e enviar um número específico de strings para o contrato inteligente. Esses números gerados serão então colocados em exibição para os usuários comprarem como tíquetes para participar da loteria. Veja o trecho de código abaixo.

Componente Auth Chat (Autenticação de Bate-Papo)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*3Kug1MCQik6ihMxS.png

Este componente autentica os usuários antes de poderem conversar com nossa plataforma. O SDK do CometChat é usado aqui de forma oculta para realizar uma autenticação com a carteira do usuário conectado. Veja o código abaixo.

Componente Chat (Bate-Papo)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*UwY2zFP05Nxxekex.png

Este componente utiliza o SDK do CometChat para realizar um bate-papo anônimo de um para muitos entre todos os usuários autenticados que também se juntaram ao grupo. Aqui está o código para sua implementação.

Componente Winners (Vencedores)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*1hBDkIMvXrndNVyG.png

Este componente é ativado quando o botão de realização do sorteio é clicado. Ele permite que você insira o número de vencedores que desejar. Veja o trecho abaixo.

Componente Result (Resultado)

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*3KfsA6SVp5wMtxVu.png

Quase tão similar quanto o componente Draw Time, este componente exibe algumas estatísticas sobre a loteria recém-concluída, os vencedores e os perdedores, o que os vencedores levaram para casa e o que os perdedores perderam. O componente também inclui um botão para realizar o sorteio, que só é habilitado quando a contagem regressiva chega a zero. Veja a implementação codificada abaixo.

Componente CometChatNoSSR

Este é um componente especial criado para nos ajudar a carregar o módulo CometChat na janela do navegador, uma vez que o NextJs é um framework de renderização no lado do servidor. Veja o código abaixo.

Aqui está um tutorial em vídeo gratuito que você pode assistir para ajudá-lo a aprender como construir uma plataforma de cunhagem de NFT descentralizada no meu canal do YouTube.

Os Componentes das Páginas

Nesta seção, vamos revisar todos os códigos que compõem cada uma das páginas deste projeto.

Por favor, observe que essas várias páginas devem ser criadas na pasta pages, no diretório raiz do seu projeto.

Página Inicial

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*p8dB2UOAFQnL3MOB.png

Esta página contém os componentes Header e Jackpots, veja sua simples implementação abaixo. Ela usa uma técnica de SSR (Server Side Rendering, ou renderização do lado do servidor) para recuperar todas as loterias da blockchain sem exigir que um usuário conecte sua carteira ou esteja em uma cadeia específica.

Página de Criação de Loterias

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*kA8Xo8JxPaoCYf4t.png

Esta página permite que um usuário crie uma loteria, e é claro, ela coleta informações sobre a loteria, como o título da loteria, descrição, URL da imagem, prêmio a ser ganho, custo do bilhete e a data de expiração da loteria. É importante notar que para criar uma loteria, a carteira do usuário deve estar conectada e na cadeia/rede correta. Veja o código abaixo.

A Página Jackpots

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*xKkdgnpuDjWk1lkl.png

Atenção, a maneira como você cria esta página é bem diferente de como as outras páginas foram criadas, pois é um componente dinâmico.

Primeiro, crie uma pasta chamada jackpots dentro do diretório pages. Em seguida, crie um arquivo chamado [jackpotId].jsx, exatamente neste formato, e cole os códigos abaixo dentro dele. Veja os códigos abaixo.

Novamente, esta página, assim como a Página Inicial, utiliza a técnica de renderização do lado do servidor do NextJs para recuperar as informações da loteria da cadeia sem exigir que os usuários façam login com seu endereço de carteira.

A Página de Resultados

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*yswMTtkSLXhZU4jY.png

Esta página, assim como a página Jackpots, usa a técnica de roteamento dinâmico do NextJs para ver o resultado de cada uma das loterias. Vá ao diretório pages e crie uma pasta chamada jackpots. Dentro desta nova pasta crie um arquivo neste formato chamado [resultId].jsx. Cole os códigos abaixo dentro dele e salve.

O arquivo _app.tsx

Este é um arquivo de entrada que vem pré-configurado com o NextJs, que você encontrará dentro da pasta pages do seu projeto. Abra-o e substitua seus códigos pelos abaixo.

Arquivos de Gerenciamento de Estado

Agora, deve ser destacado que este projeto usa o pacote redux-toolkit para manter os dados compartilhados usados em toda esta aplicação em um local centralizado. Siga as etapas abaixo para replicar isso. Antes de prosseguir para a etapa abaixo, crie uma pasta na raiz deste projeto chamada store e crie o seguinte arquivo dentro dela:

Estados Redux

Este arquivo nos ajudará a manter juntos todos os estados das variáveis que estamos usando nesta aplicação. Dentro deste diretório store, crie outra pasta chamada states e dentro dela crie um arquivo chamado global_states.js, cole os códigos abaixo dentro dele e salve.

Ações Redux

Em seguida, crie outra pasta no diretório store chamada actions e dentro dela crie um arquivo chamado global_actions.js, cole os códigos abaixo dentro dele e salve.

Redutor Redux

Vamos criar um redutor ou um slice redux que nos ajudará a gerenciar tudo que tem a ver com nossos estados e ações globais recentemente criados. Dentro da pasta store, crie um arquivo chamado global_reducer.js e salve. Veja os códigos abaixo.

Por último, vamos agrupar e nos ajudar a gerenciar todos os redutores/slices em nosso armazenamento de estado. Dentro da pasta store, crie outro arquivo chamado index.js, cole os códigos abaixo dentro dele e salve.

Podemos incluir quantos redutores quisermos aqui neste arquivo store/index.js.

Serviços

Temos três serviços usados aqui nesta aplicação que você criará em uma pasta chamada services na raiz do seu projeto.

Os serviços blockchain, blockchain.ssr, e de chat. Os serviços blockchain lida com todas as funções que enviam informações para o nosso contrato inteligente, enquanto o arquivo blockchain.ssr lê dados armazenados em nosso contrato inteligente. Isso é extremamente importante e o arquivo .ssr garante que podemos recuperar dados da blockchain sem precisar primeiro conectar nossa carteira à Metamask.

Também temos um serviço de chat que nos ajuda a comunicar com o SDK do CometChat. Veja os códigos abaixo e certifique-se de criar cada um desses arquivos na pasta services.

Blockchain.js

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,
}
Enter fullscreen mode Exit fullscreen mode

blockchain.ssr.js

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()

  // Defina a nova conta como assinante do provedor
  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,
}
Enter fullscreen mode Exit fullscreen mode

chat.js

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,
}
Enter fullscreen mode Exit fullscreen mode

Fantástico, agora vamos incluir os ativos essenciais usados neste projeto.

Ativos Estáticos

Na raiz do seu aplicativo, crie uma pasta chamada assets, baixe as imagens encontradas neste local e armazene-as nesta pasta.

Além disso, não se esqueça de instruir o NextJs a permitir que sua aplicação carregue imagens de qualquer local. Na raiz da sua aplicação, crie um arquivo chamado next.config.js, cole o código abaixo nele e salve.

E aí está, parabéns! Você criou com sucesso um aplicativo web3 de loteria, agora você só precisa rodar os seguintes comandos no seu terminal para vê-lo ao vivo no seu navegador.

yarn dev #terminal 2
Enter fullscreen mode Exit fullscreen mode

Os dois comandos acima irão colocar seu projeto online e ele pode ser visitado no navegador em localhost:3000.

https://miro.medium.com/v2/resize:fit:1100/0*clxRTZGbgrzpJxVB

Dê o primeiro passo para se tornar um desenvolvedor de contratos inteligentes altamente procurado, inscrevendo-se nos meus cursos sobre Minting de NFTs e Marketplace. Inscreva-se agora e vamos embarcar juntos nesta emocionante jornada!

Conclusão

Para concluir, esta tecnologia apresenta uma oportunidade excitante para revolucionar a indústria tradicional de jogos de azar.

O uso da tecnologia blockchain garante transparência, segurança e imutabilidade no sistema de loterias, ao mesmo tempo que elimina a necessidade de intermediários. Este tutorial sobre a construção de um DApp de Loteria com NextJs, Solidity e CometChat é um recurso valioso para desenvolvedores que desejam criar aplicações descentralizadas de ponta.

Seguindo o guia passo a passo, aprendemos como criar um sistema de loterias justo e transparente na blockchain. Então, por que não começar a construir seu próprio dApp de loterias hoje e revolucionar a indústria tradicional de jogos de azar? Não se esqueça de se inscrever no canal do YouTube.

Te vejo na próxima vez!

Sobre o Autor

Gospel Darlington é um desenvolvedor full-stack de blockchain com mais de 7 anos de experiência na indústria de desenvolvimento de software.

Ao combinar Desenvolvimento de Software, escrita e ensino, ele demonstra como construir aplicativos descentralizados em redes blockchain compatíveis com EVM.

Suas tecnologias incluem JavaScript, React, Vue, Angular, Node, React Native, NextJs, Solidity e muito mais.

Para obter mais informações sobre ele, visite e siga sua página no Twitter, Github, LinkedIn ou seu site.

Artigo original escrito por Gospel Darlington. Traduzido por Paulinho Giovannini.

Top comments (0)