WEB3DEV

Cover image for Como Construir um Jogo Cripto
Adriano P. Araujo
Adriano P. Araujo

Posted on

Como Construir um Jogo Cripto

Este tutorial técnico ensinará como criar e implantar um jogo cripto dApp full-stack na rede de testes Ethereum Goerli. O jogo que você construirá armazenará perguntas de um questionário e suas respostas na blockchain. As respostas serão hashs via keccak256, para que você possa verificar a resposta sem denunciá-la. keccak256  é uma função de hash criptográfica unidirecional e não pode ser decodificada reversamente. Isso significa que a maneira de verificar se a resposta está correta será dando um palpite e enviando-o. Se os dois hashes corresponderem, sua resposta está correta.

Este tutorial usará:

Você pode visualizar um tutorial em vídeo cobrindo todo o processo abaixo e acessar o repositório GitHub que contém o jogo aqui.

https://youtu.be/niqxn57vx9k

Configurando

Obtendo Goerli ETH

Se você não usou Goerli antes, vá para o  Chainlink Labs Faucet para obter um testnet ETH.

Instale o Foundry

Para este tutorial, você estará usando o Foundry para criar, testar e implantar seu Solidity. Você pode encontrar instruções no GitHub do Foundry .

Inicializar o projeto

Digite o seguinte no seu terminal:


❯ Development mkdir QuizGame

❯ Development cd QuizGame

❯ QuizGame forge init foundry

Initializing /Users/rg/Development/QuizGame/foundry...

Installing ds-test in "/Users/rg/Development/QuizGame/foundry/lib/ds-test", (url: https://github.com/dapphub/ds-test, tag: None)

    Installed ds-test

    Initialized forge project.

❯ Development cd foundry

❯ foundry (main) ✔




Enter fullscreen mode Exit fullscreen mode

Crie seu primeiro teste

Depois de inicializar o projeto, o Foundry cria um contrato básico e testa para você dentro do diretório src.

❯ src (main) ✔ tree

.

├── Contract.sol

└── test

    └── Contract.t.sol



1 directory, 2 files

Enter fullscreen mode Exit fullscreen mode

Eles fornecem uma ideia básica da estrutura de arquivos do Foundry. Você pode remover o Contract.sol e o Contract.t.sol, pois criará seu contrato e o teste.


❯ src ( main ) ✔ rm Contract.sol test / Contract.t.sol




Enter fullscreen mode Exit fullscreen mode

Crie QuizGame.t.sol no diretório de teste. O esqueleto básico deve ficar assim.


foundry/src/test/QuizGame.t.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;



import "ds-test/test.sol";

import "../QuizGame.sol";



interface CheatCodes {

    function deal(address, uint256) external;

}



contract QuizTest is DSTest {

    function setUp() public {}

    function testExample() public {

        assertTrue(true);

    }

}



Enter fullscreen mode Exit fullscreen mode

Isso dará a garantia de que tudo está funcionando via teste. Se você executar o teste, ele falhará devido ao contrato QuizGame não estar disponível. Isso inicia o ciclo de desenvolvimento orientado a testes, de escrever um teste, observando-o falhar, corrigindo o teste, vendo-o passar e escrevendo outro teste com falha. Você pode esperar ver erros semelhantes daqui para frente até corrigir os testes com falha. Parabéns, seus testes estão falhando conforme o esperado!


Error: 

   0: Compiler run failed

      ParserError: Source "/Users/rg/Development/QuizGame/src/QuizGame.sol" not found: File not found.

       --> /Users/rg/Development/QuizGame/src/test/QuizGame.t.sol:6:1:

        |

      6 | import "../QuizGame.sol";

        | ^^^^^^^^^^^^^^^^^^^^^^^^^



Enter fullscreen mode Exit fullscreen mode

Crie um novo arquivo chamado QuizGame.sol no diretório src.


foundry/src/QuizGame.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;



contract QuizGame {}



Enter fullscreen mode Exit fullscreen mode

Neste ponto, seu teste deve estar passando.


❯ src (main) ✘ forge test

[⠢] Compiling...

[⠆] Compiling 2 files with 0.8.13

[⠰] Solc finished in 37.90ms

Compiler run successful



Running 1 test for src/test/QuizGame.t.sol:QuizTest

[PASS] testExample() (gas: 190)

Test result: ok. 1 passed; 0 failed; finished in 1.31ms



❯ src (main) ✘




Enter fullscreen mode Exit fullscreen mode

Criando um Questionário

Agora que você tem o andaime para o contrato e os testes do jogo, você pode escrever seu primeiro teste real. Embora possa não parecer um teste, as etapas que você irá adicionar à função setup( ) garantirão a você a possibilidade de criar um questionário. Depois de criado, você poderá verificar se o questionário está armazenando corretamente as perguntas e as respostas.


foundry/src/test/QuizGame.t.sol



contract QuizTest is DSTest {

    QuizGame public game;



    function setUp() public {

        // O salt significa que dicionários pré-gerados não são válidos

        bytes32 salt = bytes32("123123123");

        // Armazena a resposta da pergunta

        string memory answer = "42";

        // Armazena a pergunta 

        string

            memory question = "What is the answer to life, the universe, and everything?";

        // Armazena a resposta correta em hash

        bytes32 hashedAnswer = keccak256(abi.encodePacked(salt, answer));

        // Cria um novo jogo com a pergunta e uma resposta em hash

        game = new QuizGame(question, hashedAnswer);

        emit log(game.question());

    }



    function testExample() public {

        assertTrue(true);

    }

}



Enter fullscreen mode Exit fullscreen mode

Para que o teste passe, atualize seu contrato.


foundry/src/QuizGame.sol



contract QuizGame {

    // O salt significa que dicionários pré-gerados não são válidos

    // alterado para qualquer valor que deseje

    bytes32 public salt = bytes32("123123123");

    // Armazena a resposta da pergunta

    bytes32 public hashedAnswer;

    // Armazena a pergunta 

    string public question;



    //  Cria o contrato de questionário com aquilo passado para questão e a resposta

    constructor(string memory _question, bytes32 _hashedAnswer) {

        // Store the hashed answer

        hashedAnswer = _hashedAnswer;

        // Store the question

        question = _question;

    }

}



Enter fullscreen mode Exit fullscreen mode

Nesse ponto, seu contrato armazenará uma pergunta e resposta, mas não faz muito mais.

Criando um Teste para Qualquer Resposta

Depois de configurar uma pergunta, você precisará verificar se o palpite que um jogador fornece corresponde à resposta armazenada no contrato de teste. Você precisa criar um teste que não falhe nisso. Vemos o fracasso como um sucesso, pois a resposta adivinhada estará incorreta.


foundry/src/test/QuizGame.t.sol



function testQuizFail() public {

// Isso irá falhar, o que significa que você precisa ‘capturar’ a falha para 'passar' o teste

    try game.guess("33") {

        assertTrue(false);

    } catch {

        assertTrue(true);

    }

}



Enter fullscreen mode Exit fullscreen mode

Este teste significa que você precisará criar uma função para aceitar um palpite. Parte dessa função fará com que os jogadores tentem adivinhar a solução para compará-la à resposta armazenada em hash.


foundry/src/QuizGame.sol



function guess(string calldata answer) public {

    // Verifica se a resposta está correta

    require(

        keccak256(abi.encodePacked(salt, answer)) == hashedAnswer,

        "Incorrect answer"

    );

}



Enter fullscreen mode Exit fullscreen mode

Essa função de adivinhação verificará se a resposta está correta e isso é tudo. Nada acontece se a resposta estiver correta. Para fornecer uma recompensa ao palpite correto, você deve enviar ETH ao contrato e pagar o jogador correto.

Traindo o acordo

O Foundry fornece um conjunto de ferramentas para manipular o estado da blockchain. Esses ‘códigos de fraude’ podem ser encontrados no livro do Foundry.

O código específico de fraude que você usará é o ponto que permitirá definir o saldo para um endereço especificado. Crie uma interface fora do contrato.


foundry/src/test/QuizGame.t.sol



interface CheatCodes {

    function deal(address, uint256) external;

}

Enter fullscreen mode Exit fullscreen mode

Dentro do contrato, você precisa usar esse código de fraude para criar trapaças constantes.


foundry/src/test/QuizGame.t.sol



contract QuizTest is DSTest {

    CheatCodes constant cheats = CheatCodes(HEVM_ADDRESS);

.

.

.



Enter fullscreen mode Exit fullscreen mode

Isso permitirá que você crie um novo teste que envie ETH  e  uma resposta correta ao contrato.


foundry/src/test/QuizGame.t.sol



function testQuizPass() public {

    // Obtém o saldo atual deste contrato

    uint256 beginBalance = address(this).balance;

    // Financia o contrato

    cheats.deal(address(game), 10000);

    //  Adivinha a resposta correta

    game.guess("42");

    // Verifica o saldo após o palpite ser 10000 a mais do que antes

    assertEq(address(this).balance, beginBalance + 10000);

}



Enter fullscreen mode Exit fullscreen mode

O objetivo é que o contrato de teste seja pago pela resposta correta. Você precisará criar uma função  fallback( ) e receive( ) para que isso aconteça.


foundry/src/test/QuizGame.t.sol

fallback() external payable {}

receive() external payable {}



Enter fullscreen mode Exit fullscreen mode

Respondendo a uma Pergunta

O contrato deve transferir seu saldo para o usuário quando uma pergunta for respondida corretamente.


foundry/src/QuizGame.sol



function guess(string calldata answer) public {

    require(

        keccak256(abi.encodePacked(salt, answer)) == hashedAnswer,

        "Incorrect answer"

    );

    // Se o contrato tem saldo e a resposta é correta

    if (address(this).balance > 0) {

        // envia o saldo para aquele que acertar o palpite

        (bool sent, bytes memory data) = payable(msg.sender).call{value: address(this).balance}("");

    }

}

fallback() external payable {

}



receive() external payable {

}

Enter fullscreen mode Exit fullscreen mode

Fantástico! Neste ponto, você tem um contrato de teste totalmente funcional. Existem mais algumas etapas para concluir o trabalho em Solidity antes de começarmos o front-end. Primeiro, você precisa criar alguns eventos no contrato QuizGame.

Adicionando eventos

Os eventos permitirão que você observe as alterações no estado do contrato. Para o questionário, você irá deseja criar um evento para o financiamento do questionário e para as supostas respostas corretas.

No topo do contrato, adicione os dois eventos. Adicione-os um após o outro


string public quesiton;




Enter fullscreen mode Exit fullscreen mode



foundry/src/QuizGame.sol



event QuizFunded(uint256 balance);

event AnswerGuessed();




Enter fullscreen mode Exit fullscreen mode

No final da função de adivinhação, adicione:


emit AnswerGuessed();




Enter fullscreen mode Exit fullscreen mode

Adicione o respectivamente às funções de fallback e receive::


emit QuizFunded(address(this).balance);



Enter fullscreen mode Exit fullscreen mode

Criando Uma Fábrica

A peça final do quebra-cabeça para o seu jogo de perguntas e respostas é a fábrica. O que exatamente é um contrato de fábrica? Um contrato de fábrica é um contrato que cria um conjunto de outros contratos e os acompanha. Você precisará criar dois novos arquivos para a fábrica, o contrato e o teste.


foundry/src/test/QuizFactory.t.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;



import "ds-test/test.sol";

import "../QuizFactory.sol";



contract QuizFactoryTest is DSTest {

    QuizFactory public factory;



    function setUp() public {

        factory = new QuizFactory();

    }

}

Enter fullscreen mode Exit fullscreen mode

foundry/src/QuizFactory.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;



import "./QuizGame.sol";



contract QuizFactory {

    constructor() {}

}

Enter fullscreen mode Exit fullscreen mode

Esta fábrica básica precisará de mais algumas adições. Primeiro, você deve adicionar um teste para testar a criação de um questionário via fábrica.

Crie um Questionário na Fábrica

O primeiro passo é adicionar um teste para criar um questionário a partir do contrato de fábrica.


foundry/src/test/QuizFactory.t.sol



function testCreateQuiz() public {

    // Define uma resposta

    string memory answer = "42";

    // Define uma pergunta

    string

        memory question = "What is the answer to life, the universe, and everything?";

    // Define um hash para a resposta

    bytes32 salt = bytes32("123123123");

    bytes32 hashedAnswer = keccak256(abi.encodePacked(salt, answer));

    // Cria um novo questionário com a pergunta e o hash da resposta    factory.createQuiz(question, hashedAnswer);

    // Obtém um novo questionário

    QuizGame quiz = factory.quizzes(0);

    // Verifica a pergunta

    assertEq(

        keccak256(abi.encodePacked(quiz.question())),

        keccak256(abi.encodePacked(question))

    );

}

Enter fullscreen mode Exit fullscreen mode

Este teste espera algumas coisas. Primeiro, uma função createQuiz. Também espera que você tenha uma maneira de fazer referência aos questionários do contrato de fábrica. Você precisará criar uma matriz de QuizGames, que estará disponível publicamente.

Configure a matriz na parte superior do seu contrato QuizFactory. Pode ser bom ter um evento disponível também. Quando você começar a construir o front-end, seu futuro eu o agradecerá.


foundry/src/QuizFactory.sol



QuizGame[] public quizzes;

event QuizCreated(QuizGame indexed quiz, address indexed creator);

Enter fullscreen mode Exit fullscreen mode

Agora você pode criar a função createQuiz


foundry/src/QuizFactory.sol



function createQuiz(string memory _question, bytes32 _answer) public {

    // Cria um novo questionário

    QuizGame quiz = new QuizGame(_question, _answer);

    // Adiciona-o para uma lista de quiestionários

    quizzes.push(quiz);

    // Emite o evento

    emit QuizCreated(quiz, msg.sender);

}

Enter fullscreen mode Exit fullscreen mode

Você pode adicionar questionários e seria útil retornar todos os questionários que a fábrica criou.


function testCountquizzes() public {

    // Define a resposta

    string memory answer = "42";

    // Define a pergunta

    string

        memory question = "What is the answer to life, the universe, and everything?";

    // Define um hash para a resposta

    bytes32 salt = bytes32("123123123");

    bytes32 hashedAnswer = keccak256(abi.encodePacked(salt, answer));

    // Cria dois novos questionários

    factory.createQuiz(question, hashedAnswer);

    factory.createQuiz(question, hashedAnswer);

    // Pega todos os questionários

    QuizGame[] memory quizzes = factory.getQuizzes();

    // Verifica o número de questionários

    assertEq(quizzes.length, 2);

}

Enter fullscreen mode Exit fullscreen mode

Este teste verifica se o número de questionários retornados corresponde ao que você espera. Você precisa criar a função getQuizzes, que deve retornar uma matriz de QuizGames


foundry/src/test/QuizFactory.sol



function getQuizzes() public view returns (QuizGame[] memory col) {

    // Calcula o número de questionários

    uint256 size = quizzes.length;

    // Cria um novo array para os questionários

    col = new QuizGame[](size);

    // Cópia os questionários para o novo array

    for (uint256 i = 0; i < size; i++) {

        col[i] = quizzes[i];

    }

    // Retorna o array

    return col;

}

Enter fullscreen mode Exit fullscreen mode

Contrato concluído!

Você conseguiu! O contrato está pronto para ser implantado! Você pode usar esse script para implantá-lo. Salve-o na raiz do seu projeto.


deploy.sh

#!/usr/bin/env bash



# Lê a URL RPC

echo Enter Your RPC URL:

echo Example: "https://eth-goerli.alchemyapi.io/v2//XXXXXXXXXX"

read -s rpc



# Lê o nome do contrato

echo Which contract do you want to deploy \(eg Greeter\)?

read contract



forge create ./src/${contract}.sol:${contract} -i --rpc-url $rpc



Enter fullscreen mode Exit fullscreen mode

Isso permitirá que você implante seu contrato no Goerli, e tudo o que isso está fazendo é ler variáveis sem exibir o que você está digitando na linha de comando. Isso garantirá que suas chaves privadas não sejam armazenadas no histórico da sua linha de comando.

Depois de executar o deploy.sh, você deve ver para onde seu contrato foi implantado. Você precisará deste endereço para a próxima seção, em que construiremos o front-end.


Deployer: 0x0000000000000000000000000000000000000000

Deployed to: 0x1234567890123456789012345678901234567890

Transaction hash: 0x1234567890123456789012345678901234567890594be2f670606ada53412aaa

Enter fullscreen mode Exit fullscreen mode

Instalação do Svelte

Inicializar o Svelte é simples. Para ver as instruções, você pode ir para a Página inicial do SvelteKit. Para este tutorial, você deve usar o seguinte. Isso criará um esqueleto do projeto no diretório Svelte.


❯ QuizGame (main) ✘ npm init svelte svelte

Need to install the following packages:

  create-svelte

Ok to proceed? (y) y

create-svelte version 2.0.0-next.139

Welcome to SvelteKit!

This is beta software; expect bugs and missing features.

Problems? Open an issue on https://github.com/sveltejs/kit/issues if none exists already.

✔ Which Svelte app template? › Skeleton project

✔ Add type checking? › None

✔ Add ESLint for code linting? … Yes

✔ Add Prettier for code formatting? … Yes

✔ Add Playwright for browser testing? … No

Your project is ready!

✔ ESLint

  https://github.com/sveltejs/eslint-plugin-svelte3

✔ Prettier

  https://prettier.io/docs/en/options.html

  https://github.com/sveltejs/prettier-plugin-svelte#options



Install community-maintained integrations:

  https://github.com/svelte-add/svelte-adders



Next steps:

  1: cd svelte

  2: npm install (or pnpm install, etc)

  3: git init && git add -A && git commit -m "Initial commit" (optional)

  4: npm run dev -- --open

To close the dev server, hit Ctrl-C



Stuck? Visit us at https://svelte.dev/chat

❯ QuizGame (main) ✘ cd svelte

Enter fullscreen mode Exit fullscreen mode

Você também precisará adicionar ethers ao projeto


❯ svelte (main) ✘ npm install ethers

added 43 packages, and audited 180 packages in 1s

51 packages are looking for funding

  run `npm fund` for details

found 0 vulnerabilities

Enter fullscreen mode Exit fullscreen mode

Você deve iniciar o servidor Svelte e ver a página a seguinte página.


npm run dev -- --open



Enter fullscreen mode Exit fullscreen mode

Conectando sua carteira

Você precisará criar um componente no diretório svelte/src/liblib, você também precisará criar este diretório. Para iniciar e garantir que tudo esteja funcionando, crie um único botão no componente por enquanto.


svelte/src/lib/WalletConnect.svelte



<button>Attach Wallet</button>

Enter fullscreen mode Exit fullscreen mode

Então dentro de svelte/src/routes/index.svelte, você pode importar este novo componente. Garantir que o componente seja importado corretamente é uma excelente prática antes de desenvolvê-lo por completo. Ele também permite que você veja alterações incrementais ao criar o componente por meio de atualizações.


svelte/src/routes/index.svelte



<script>

    import WalletConnect from '$lib/WalletConnect.svelte';

</script>



<h1>My Quiz</h1>



<WalletConnect />

Enter fullscreen mode Exit fullscreen mode

Isso deve fornecer a seguinte alteração na sua página.

Quando isso estiver funcionando, podemos construir o restante dos componentes. Não vou demonstrar essas etapas daqui para frente, mas lembre-se de criar os componentes antes de importá-las.

Para passar o contrato e a carteira entre os componentes, você precisará criar um local para armazená-los. Você pode criar um objeto web3Props que conterá essas informações.


svelte/src/lib/WalletConnect.svelte



<script>

    import { ethers } from 'ethers';

    // espaço reservado para as propriedades que passaremos entre os componentes

    export let web3Props = { provider: null, signer: null, account: null, chainId: null };

    // conecta com a carteira

    async function connectWallet() {

        // obtém o provedor, desta vez sem objeto ethereum

        let provider = new ethers.providers.Web3Provider(window.ethereum, 'any');

        // solicita ao usuário conexões de conta

        await provider.send('eth_requestAccounts', []);

        // obtém o signatário

        const signer = provider.getSigner();

        //obtém o endereço da conta

        const account = await signer.getAddress();

        // obtém o chainId

        const chainId = await signer.getChainId();

        // atualiza os props

        web3Props = { signer, provider, chainId, account };

    }

</script>



<button on:click={connectWallet}>Attach Wallet</button>

Enter fullscreen mode Exit fullscreen mode

Depois que o componente for atualizado, você precisará passar os props do index.svelte para o componente.


svelte/src/routes/index.svelte



<script>

    import WalletConnect from '$lib/WalletConnect.svelte';



    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null

    };

</script>



<h1>My Quiz</h1>

{#if !web3Props.account}

    <WalletConnect bind:web3Props />

{:else}

    😎

{/if}

Enter fullscreen mode Exit fullscreen mode

Criando um questionário

Você conectou sua carteira! Agora você pode continuar interagindo com a Quiz Factory para criar uma pergunta.

O primeiro passo será trazer os ABI’s para os dois contratos. Quando os contratos foram compilados via Forge para teste ou implantação, foi criado um arquivo contendo uma versão JSON do ABI;

out/QuizFactory.sol/QuizFactory.json e ambos são criados no diretório Foundry. Crie um novo diretório em svelte/src nomeado contracts e copie os dois arquivos lá. Isso permitirá que você interaja com as versões implantadas dos contratos.

Depois que os dois arquivos JSON forem salvos no novo diretório de contratos, você poderá criar um componente para adicionar um questionário.


svelte/src/lib/AddQuestion.svelte



<script>

    // ethers permitem a você interajir com a blockchain Ethereum 

    import { ethers } from 'ethers';

    // web3Props detém as propriedades do provedor web3

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null,

        contract: null

    };

    //  valores para o contrato de fábrica de questionários

    $: question = '';

    $: answer = '';

    $: encryptedAnswer = null;

    async function encryptAnswer() {

        // Encripta a resposta usando o mesmo salt como o contrato

        encryptedAnswer = ethers.utils.keccak256(

            ethers.utils.solidityPack(

                ['bytes32', 'string'],

                [ethers.utils.formatBytes32String('123123123'), answer]

            )

        );

        //Usa o contrato fábrica para criar um novo questionário

        web3Props.contract.createQuiz(question, encryptedAnswer);

    }

</script>



<div class="wrapper">

    <span class="input-label"> question: </span>

    <!--o bind permite que as alterações na pergunta atualizem o valor da variável-->

    <input bind:value={question} />

    <br />

    <span class="input-label"> answer: </span>

    <input bind:value={answer} />

    <br />

    <!-- O On click, executa a função encryptedAnswer-->

    <button on:click={encryptAnswer}> Add Question </button>

</div>



<!-- escopo do CSS -->

<style>

    .wrapper {

        overflow: hidden;

        position: relative;

        margin-bottom: 1rem;

        padding: 20px;

        border-radius: 15px;

        width: 33%;

        box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3);

    }

    .input-label {

        display: inline-block;

        width: 15%;

    }

</style>

Enter fullscreen mode Exit fullscreen mode

Você deseja garantir que adicionou este componente ao index.svelte


svelte/src/routes/index.svelte



<script>

    import WalletConnect from '$lib/WalletConnect.svelte';



    // NOVO

    import AddQuestion from '$lib/AddQuestion.svelte';

    import contractAbi from '../contracts/QuizFactory.json';

    const contractAddr = "<YOUR CONTRACT ADDRESS HERE>;



    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null

    };

</script>



<h1>My Quiz</h1>

{#if !web3Props.account}

    <WalletConnect bind:web3Props />

{:else}

<!-- NEW -->

    <AddQuestion {web3Props} />

{/if}

Enter fullscreen mode Exit fullscreen mode

Por favor note: Você precisará do endereço do contrato registrado na seção “ contract completed ” acima. Este será o valor do contractAddr

Adicione os novos valores ao componente WalletConnect no index.svelte


<WalletConnect bind:web3Props {contractAddr} {contractAbi} />



Enter fullscreen mode Exit fullscreen mode

Adicione-os ao próprio componente.


svelte/src/lib/WalletConnect.svelte



<script>

    import { ethers } from 'ethers';

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null,

        // new prop

        contract: null

    };

    // nova variável para o endereço do contrato

    export let contractAddr = '';

    // nova variável para o contrato  ABI

    export let contractAbi = { abi: null };



    async function connectWallet() {

        let provider = new ethers.providers.Web3Provider(window.ethereum, 'any');

        await provider.send('eth_requestAccounts', []);

        const signer = provider.getSigner();

        const account = await signer.getAddress();

        const chainId = await signer.getChainId();

        //nova variável do contrato

        const contract = new ethers.Contract(contractAddr, contractAbi.abi, signer);

        //novo valor para o contrato

        web3Props = { signer, provider, chainId, account, contract };

    }

</script>



<button on:click={connectWallet}>Attach Wallet</button>

Enter fullscreen mode Exit fullscreen mode

Exibir uma pergunta

Vá em frente e adicione uma pergunta. Isso usará o contrato  quizFactory para criar um novo quizGame. Depois de confirmar a transação, seria ótimo ver os testes. Você precisará criar um componente de pergunta. Este componente exibirá uma única pergunta. Você reutilizará este componente para exibir todas as perguntas em breve.


svelte/src/lib/Question.svelte



<script>

    import { ethers } from 'ethers';

    // importa o único  contrato de jogo

    import contractAbi from '../contracts/QuizGame.json';

    // espaços reservados para variáveis

    let answer = null;

    let funding = null;

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null,

        contract: null

    };

    export let questionAddr = null;

    $: question = null;

    $: value = null;

    // O financiamento determinará qual CSS e funcionalidade estarão disponíveis

    $: funded = value > 0 ? 'question-funded' : 'question-not-funded';

    let qContract = null;

    async function getQuestion() {

        // obtém o contrato de pergunta

        qContract = new ethers.Contract(questionAddr, contractAbi.abi, web3Props.signer);

        // Obtém a pergunta

        question = await qContract.question();

        //Obtém o valor da resposta correta

        value = Number(ethers.utils.formatEther(await web3Props.provider.getBalance(questionAddr)));

        // Observa os fundos

        qContract.on('QuizFunded', (balance) => {

            console.log('QuizFunded', balance);

            value = Number(ethers.utils.formatEther(balance));

        });

        // Observa as respostas corretas

        qContract.on('AnswerGuessed', () => {

            getQuestion();

        });

    }

    // Submete um palpite ao contrato

    async function submitGuess() {

        await qContract.guess(answer);

    }

    // financia a pergunta

    async function fund() {

        web3Props.signer.sendTransaction({

            to: questionAddr,

            value: ethers.utils.parseEther(funding)

        });

        funding = null;

    }

    // executa a função getQuestion 

    getQuestion();

</script>



<!-- Com base no financiamento, altera a classe CSS -->

<div class="{funded} qwrap">

    <div class="question">

        {question}

    </div>

    <div class="value">

        {value} ETH

    </div>

    <input type="text" bind:value={answer} />

    <!--Se a pergunta não tiver valor, ela é desativada -->

    <button on:click={submitGuess} disabled={value <= 0}>Submit Answer</button>

    <br />

    <input type="text" bind:value={funding} />

    <button on:click={fund}>Fund</button>

</div>



<style>

    .question-funded {

        background: #4ee44e;

    }

    .question-not-funded {

        background: #ffb6c1;

    }

    .qwrap {

        overflow: hidden;

        position: relative;

        color: white;

        margin-bottom: 1rem;

        padding: 20px;

        border-radius: 15px;

        width: 50%;

        box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3);

    }

    .question {

        font-size: 2em;

    }

</style>

Enter fullscreen mode Exit fullscreen mode

Adicione seu novo componente ao index.svelte


svelte/src/routes/index.svelte



<script>

    import WalletConnect from '$lib/WalletConnect.svelte';

    import AddQuestion from '$lib/AddQuestion.svelte';

    import contractAbi from '../contracts/QuizFactory.json';

    // NOVO

    import Question from '$lib/Question.svelte';

    const contractAddr = '0xe7608e790a0ac33014fdeaef9c8bf0c37bf443f0';

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null

    };

</script>



<h1>My Quiz</h1>

{#if !web3Props.account}

    <WalletConnect bind:web3Props {contractAddr} {contractAbi} />

{:else}

    <AddQuestion {web3Props} />

    <!-- NOVO -->

    <Question {web3Props} />

{/if}

Enter fullscreen mode Exit fullscreen mode

Neste ponto, você deve ver o seguinte.

Isso se deve a não aprovação em um endereço de contrato. Você pode passar no endereço, se quiser, ou seguir em frente para obter todos os testes.

Todas as perguntas do questionário

Parabéns, você chegou ao passo final!

Depois de ter um componente de questionário único, você pode reverter as alterações feitas em index.svelte . Você adicionará um novo componente :AllQuestions.  Vá em frente e crie-o.


svelte/src/lib/AllQustions.svelte

<script>

    // importa componente  de pergunta

    import Question from './Question.svelte';

    // variáveis para o contrato

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null,

        contract: null

    };

    $: questions = null;

    // obtém TODAS as perguntas

    async function getQuestions() {

        questions = await web3Props.contract.getQuizes();

        // Observa as novas perguntas

        web3Props.contract.on('QuizCreated', (addr) => {

            console.log('QuizCreated', addr);

            getQuestions();

        });

    }

    getQuestions();

</script>

<!--Se houverem perguntas -->

{#if questions}

    <div class="question-wrapper">

        <!--Percorre todas as perguntas -->

        {#each questions as questionAddr}

            <!-- Renderiza o componente de pergunta -->

            <Question {questionAddr} {web3Props} />

        {/each}

    </div>

{/if}

<style>

    .question-wrapper {

        display: flex;

        flex-direction: column;

        justify-content: center;

        align-items: center;

    }

</style>

Enter fullscreen mode Exit fullscreen mode

Atualize index.svelte para usar o AllQuestions


svelte/src/routes/index.svelte



<script>

    import WalletConnect from '$lib/WalletConnect.svelte';

    import AddQuestion from '$lib/AddQuestion.svelte';

    import contractAbi from '../contracts/QuizFactory.json';

    // NOVO

    import AllQuestions from '$lib/AllQuestions.svelte';

    const contractAddr = '0xe7608e790a0ac33014fdeaef9c8bf0c37bf443f0';

    export let web3Props = {

        provider: null,

        signer: null,

        account: null,

        chainId: null

    };

</script>



<h1>My Quiz</h1>

{#if !web3Props.account}

    <WalletConnect bind:web3Props {contractAddr} {contractAbi} />

{:else}

    <AddQuestion {web3Props} />

    <!-- NEW -->

    <br />

    <br />

    <AllQuestions {web3Props} />

{/if}

Enter fullscreen mode Exit fullscreen mode

Agora você tem todas as suas perguntas de teste exibidas!

A partir daqui, você pode adicionar mais perguntas ao questionário ou financiar as existentes. Uma vez financiadas, você poderá respondê-las.

Resumo

Neste tutorial, usamos o desenvolvimento orientado a testes para criar um conjunto de contratos que nos permitem armazenar respostas em hash para perguntas na blockchain. Essas respostas são armazenadas de maneira segura que impede os participantes de trapacear. Também conectamos um front-end em SvelteKit à blockchain, usando a carteira de um participante e permitimos que ele adicionasse e respondesse perguntas.

A partir daqui, você pode criar seções de questionário mais completas ou talvez trabalhar na exibição do front-end. Uma ideia interessante seria usar  Chainlink Automation para limitar a janela de tempo em que uma pergunta fica disponível para ser respondida ou permitir que vários “vencedores ” dividam o prêmio final.

Saiba mais sobre o Chainlink visitando  chain.link  ou lendo a documentação em  docs.chain.link. Para discutir integração, procure um especialista.


Este artigo foi escrito por Richard Gottleber  e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)