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.
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) ✔
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
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
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);
}
}
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";
| ^^^^^^^^^^^^^^^^^^^^^^^^^
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 {}
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) ✘
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);
}
}
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;
}
}
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);
}
}
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"
);
}
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;
}
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);
.
.
.
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);
}
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 {}
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 {
}
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;
foundry/src/QuizGame.sol
event QuizFunded(uint256 balance);
event AnswerGuessed();
No final da função de adivinhação, adicione:
emit AnswerGuessed();
Adicione o respectivamente às funções de fallback e receive::
emit QuizFunded(address(this).balance);
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();
}
}
foundry/src/QuizFactory.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./QuizGame.sol";
contract QuizFactory {
constructor() {}
}
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))
);
}
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);
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);
}
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);
}
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;
}
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
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
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
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
Você deve iniciar o servidor Svelte e ver a página a seguinte página.
npm run dev -- --open
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>
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 />
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>
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}
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>
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}
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} />
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>
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>
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}
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>
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}
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)