Esta é uma tradução de Bernardo Perelló, o original em inglês de Alexandr Kumancev pode ser encontrado aqui.
Este é um caso de uso de NFT Whitelist. Saiba como interagir com um contrato inteligente Solidity implantado usando React.js e ethers.js
Trabalhar nos componentes técnicos dos NFTspode resultar em uma infinidade de recursos que devem ser implementados, e esses recursos são codificados no contrato inteligente e no site de cunhagem/roteiro do projeto.
Essencialmente, um DApp no qual os usuários podem se comunicar com o contrato inteligente por meio do frontend.
Este projeto demonstra um caso de uso básico do whitelistening.
Introdução
Neste post, criaremos um DApp completo que será implantado na Ethereum.
Solidity é o conjunto de tecnologia do nosso contrato inteligente, Alchemy é nosso RPC e Hardhat é nosso ambiente de desenvolvimento local.
Para lidar com toda a nossa funcionalidade de interação de blockchain, usaremos React.js e ethers.js no front-end.
Pre-requesitos
Tudo o que é necessário é um conhecimento básico da linguagem de programação Solidity e uma compreensão intermediária de React.js.
Vamos construir algo
O plano é construir e implantar primeiro o contrato inteligente e, posteriormente, o front-end.
Nas linhas a seguir, vamos configurar nosso ambiente Hardhat e então escrever algum código Solidity.
Por fim, financiaremos nossa carteira com tokens de teste e lançaremos nosso contrato na testnet!
Iremos em frente e receberemos nosso endereço de contrato e abi do contrato. Ele atuará como uma rota de comunicação do contrato com a blockchain.
Vamos então colocar a mão na massa.
Preparando nosso ambiente
Construiremos e implantaremos nosso contrato inteligente localmente usando o ambiente Hardhat.
- Crie uma pasta para seu contrato inteligente na raiz ou em qualquer lugar que desejar.
- Para configurar o ambiente,
cd
na pasta raiz e execute os seguintes comandos.
// Nessa ordem, rode os seguintes comandos
npm init --yes
npm install --save-dev hardhat
npx hardhat
Depois de executar o npx hardhat
, você receberá algumas configurações para configurar:
- Selecione Criar um projeto de amostra básico.
- Pressione enter para a raiz do Projeto Hardhat já especificada.
- Pressione enter para a pergunta se você quiser adicionar um
.gitignore
. Conclua a instalação do Hardhat instalando esses pacotes. Copie e cole isso no seu terminal:npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
É isso! Podemos começar a escrever algum código.
O Contrato inteligente Whitelist
Depois de instalar o Hardhat e as outras dependências necessárias, podemos ver que as pastas contracts
, scripts
e test
incluem o contrato inteligente de amostra Greeting.sol
, bem como seus scripts de implantação e teste.
Certifique-se de que todas essas pastas estejam vazias porque criaremos um novo contrato inteligente desde o início!
Crie um novo arquivo na pasta de contratos
e nomeie-o como Whitelist.sol
.
- O contrato incluirá um
uint public maxWhitelistedAddresses
que representa o número máximo de contas que podem estar em nossa lista de permissões. Ninguém pode entrar na Whitelist se esse número for alcançado. Definiremos o valor para essa entidade com uma função construtora quando estivermos implantando. - O contrato incluirá um
uint public numAddressesWhitelisted
que representa o número atual de contas que já temos em nossa lista de permissões. Na implantação, o valor desse valor será zero e será aumentado até que o máximo seja atingido. - O contrato incluirá um
mapping(address => bool) public whitelistedAddresses
. Essa lógica define um endereço específico comotrue
e imediatamente ele se junta à lista de permissões. Ele também será usado para garantir que nenhuma conta possa entrar na lista de permissões duas vezes! - Além disso, teremos uma função
constructor
que será usada para definir o valor do máximo de contas que podem entrar na whitelist, ou seja,maxWhitelistedAddress
. O valor será definido na implantação.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract Whitelist {
// Representa o número total de contas que queremos ter em nossa lista de permissões.
// O valor disso será definido com o construtor quando implantarmos.
uint public maxWhitelistedAddresses;
// Essa lógica cria um mapeamento de endereço para booleano
// valor padrão é false. Ele será definido como verdadeiro quando um endereço se juntar.
mapping(address => bool) public whitelistedAddresses;
// Esta variável acompanhará o número de endereços na lista de permissões.
// Aumentará até que o número máximo seja alcançado.
uint public numAddressesWhitelisted;
// Recebe uma entrada que definirá o valor de maxWhitelistAddress
// Proprietário colocará o valor no momento da implantação
constructor(uint _maxWhitelistedAddresses) {
maxWhitelistedAddresses = _maxWhitelistedAddresses;
}
}
Agora que temos todos os nossos tipos, um mapeamento e um construtor, precisamos completar este contrato adicionando a função mais importante addAddressToWhitelist()
.
Esta função será chamada quando o usuário quiser entrar na lista de permissões:
function addAddressToWhitelist() public {
// garante que o chamador da função ainda não faça parte da lista de permissões.
require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted");
// verifique se o número máximo de endereços na lista de permissões não foi atingido, caso contrário, lance um erro.
require(numAddressesWhitelisted < maxWhitelistedAddresses, "You cannot join now. Limit has been reached");
// Define o endereço dos chamadores como verdadeiro.
// Isso o torna um endereço legível na lista de permissões
whitelistedAddresses[msg.sender] = true;
// Isso aumentará o número de endereços na lista de permissões
numAddressesWhitelisted += 1;
}
Juntando todo o nosso contrato
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract Whitelist {
// Representa o número total de contas que queremos ter em nossa lista de permissões.
// O valor disso será definido com o construtor quando implantarmos.
uint public maxWhitelistedAddresses;
// Essa lógica cria um mapeamento de endereço para boolean
// valor padrão é false. Ele será definido como verdadeiro quando um endereço se juntar.
mapping(address => bool) public whitelistedAddresses;
// Esta variável acompanhará o número de endereços na lista de permissões.
// Aumentará até que o número máximo seja alcançado.
uint public numAddressesWhitelisted;
// Recebe uma entrada que definirá o valor de maxWhitelistAddress
// Proprietário colocará o valor no momento da implantação
constructor(uint _maxWhitelistedAddresses) {
maxWhitelistedAddresses = _maxWhitelistedAddresses;
}
function addAddressToWhitelist() public {
// garante que o chamador da função ainda não faça parte da lista branca.
require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted");
// verifica se o número máximo de endereços permitidos não foi alcançado, caso contrário, gera um erro.
require(numAddressesWhitelisted < maxWhitelistedAddresses, "You cannot join now. Limit has been reached");
// Define o endereço dos chamadores como true.
// Isso o torna um endereço legível na lista de permissões
whitelistedAddresses[msg.sender] = true;
// This will increase the number of whitelisted addresses
numAddressesWhitelisted += 1;
}
}
Configurando o script de implantação
Na pasta scripts
, crie um arquivo deploy.js
. É dentro desse arquivo que teremos toda nossa lógica de implantação e construtores de valores definidos.
const { ethers } = require("hardhat");
async function main() {
/*
Um ContractFactory em ethers.js é uma abstração usada para implantar novos contratos inteligentes, então `ourContract` se refere a uma fábrica para instâncias do nosso contrato Whitelist.
*/
const ourContract = await ethers.getContractFactory("Whitelist");
// aqui implantamos o contrato
const deployedContract = await ourContract.deploy(10);
// 10 é o número máximo de endereços permitidos na lista de permissões, ou seja, nosso construtor aguarda que termine de implantar
await deployedContract.deployed();
// imprime e registra o endereço do contrato implantado no console
console.log(
"Contract Address:",
deployedContract.address
);
}
// Chama a função main e catch se houver algum erro
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Financiando nossa carteira e obtendo nosso RPC
Financiando nossa carteira
Para lançar nosso contrato inteligente, financiaremos nossa carteira Metamask com algumas moedas testnet (falsas) por motivos de desenvolvimento.
- Visite este link para financiar sua carteira com algum ETH falso.
- Antes de fazer a solicitação, inicie o Metamask e mude para a rede de teste Goerli (devido às alterações de protocolo do Ethereum: as redes de teste Rinkeby, Ropsten e Kovan podem não funcionar de forma confiável e serão descontinuadas em breve. Saiba mais).
Obtendo nosso RPC
Alchemy é um serviço de API blockchain que nos permitirá implantar facilmente nosso contrato inteligente!
Inscreva-se e, em seguida, vamos criar nosso aplicativo e adquirir nossas chaves de API.
- Após a inscrição bem-sucedida, crie um aplicativo no painel.
- Preencha alguns dados pertinentes para concluir a configuração do seu aplicativo. Selecione Ethereum e a Rede de Teste Goerli.
- Veja os detalhes do seu aplicativo e copie seu endpoint HTTP. Em breve, usaremos esse endpoint para a implantação do contrato.
Vamos finalizar a implantação
- Crie um arquivo
.env
na raiz de sua pasta de contrato inteligente. Este arquivo conterá seu endpoint HTTP Alchemy, bem como sua Chave Privada Goerli. Alterne para a rede de teste Rinkeby no Metamask e copie a chave privada.
Em seu arquivo .env:
ALCHEMY_API_KEY_URL="add-your-alchemy-endpoint-url-here"
GOERLI_PRIVATE_KEY="add-your-goerli-private-key-here"
Para poder importar nossas chaves para nosso arquivo de configuração final, precisamos de um pacote npm chamado dotenv.
npm install dotenv
Configuração final antes da implantação
Navegue até o arquivo hardhat.config.js
. Este arquivo será usado para configurar nossas redes e chaves de API para o processo de implantação.
Copie e cole as seguintes linhas de código no arquivo por conveniência.
require("@nomiclabs/hardhat-waffle");
require("dotenv").config({ path: ".env" });
const API_KEY = process.env.API_KEY;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
module.exports = {
solidity: "0.8.0",
networks: {
goerli: {
url: API_KEY,
accounts: [PRIVATE_KEY],
},
},
};
Compilar o contrato
npx hardhat compile
Implantar o contrato
npx hardhat run scripts/deploy.js --network goerli
Pronto! Nosso contrato foi implantado com sucesso na Ethereum Rinkeby Test Network!
Anote o endereço do contrato que foi inserido no console. Será necessário no frontend para se comunicar com o contrato inteligente.
Uma nova pasta artifacts
é criada após uma implantação bem-sucedida. Esta pasta contém um arquivo .json com a ABI do nosso contrato. Copie-o e salve-o, pois vamos precisar dele junto com o endereço do contrato.
O desenvolvimento de front-end da Whitelist
Agora que completamos o aspecto difícil, vamos construir nosso front. Comunicar-se diretamente com um contrato inteligente é difícil para uma pessoa comum.
É nossa responsabilidade como desenvolvedores criar uma interface interativa a partir da qual os usuários podem enviar solicitações ao contrato inteligente.
Na segunda e última parte deste tutorial, vamos criar um aplicativo React, conectá-lo à Metamask e usar ethers.js
para chamar funções.
Essencialmente, nosso aplicativo aproveitará os princípios básicos do React, como gerenciar e atualizar estados e chamar transações por meio de funções. Nada muito complicado. Te darei cobertura.
Instalando React.js e ethers.js
- Visite os documentos oficiais para instalar e configurar o
React.js
em seu computador. - Após instalar o React com sucesso, instale o
ethers.js
como uma dependência executando este código:npm install ethers
- Por uma questão de simplicidade e conveniência, vou fazer todo o meu raciocínio na raiz do arquivo
src
. Você pode organizar a estrutura de pastas como achar melhor.
Construindo o front-end
No arquivo Whitelist.js:
import { useState } from "react";
export default function Whitelist() {
// isso gerencia o estado do número de pessoas na lista de permissões
// valor inicial é definido como `null`
const [numofWhitelisted, setNumofWhitelisted] = useState(null);
// isso gerencia o texto mostrado no botão quando conectado ou não.
// o valor inicial é definido em `Connect Wallet`
const [connectButtonText, setConnetButtonText] = useState("Connect Wallet");
// isso gerencia o estado da conta / endereço conectado
// valor inicial é definido como `null`
const [connectedAddress, setConnectedAddress] = useState(null);
return (
<div>
<div>
<div>
<h1>Welcome to the Whitelist Tutorial!</h1>
<div>
// quando este botão é clicado, o Metamask deve aparecer para o usuário se conectar
// após a conexão bem-sucedida, o estado do texto deve mudar.
<button>
{connectButtonText}
</button>
// após a conexão bem-sucedida, o estado do texto deve mudar
// o endereço da conta conectada deve aparecer
<p> Connected Address : {connectedAddress} </p>
</div>
<div>
// quando este botão é clicado, o estado deve ser atualizado e o número da lista de permissões deve mostrar
<button>
Get the number of whitelisted addresses
</button>
{numofWhitelisted} have already joined the Whitelist
</div>
<div>
// quando este botão é clicado, a Metamask deve aparecer para que uma transação seja assinada
// quando assinado, o usuário será adicionado à lista de permissões
<button>
{joinWhiteListText}
</button>
</div>
</div>
</div>
<footer>Made by Alex Kumancev</footer>
</div>
);
}
olidity: "0.8.0",
networks: {
goerli: {
url: API_KEY,
accounts: [PRIVATE_KEY],
},
},
};
Agora você tem o aplicativo sem filtros, implementando todas as funções da blockchain e atualizando os estados.
import { useState } from "react";
import { ethers } from "ethers";
// salve sua ABI em um arquivo json e importe aqui
import contract_abi from "./contract_abi.json";
export default function Whitelist() {
// endereço do contrato inteligente
const contractAddress = `0x90b989349A58a20415Cb3ff440b6244cF3737e12`;
// isso gerencia o estado do número de pessoas na lista de permissões
// valor inicial é definido como `null`
const [numofWhitelisted, setNumofWhitelisted] = useState(null);
// isso gerencia o texto mostrado no botão quando conectado ou não.
// o valor inicial é definido em `Connect Wallet`
const [connectButtonText, setConnetButtonText] = useState("Connect Wallet");
// isso gerencia o estado da conta / endereço conectado
// valor inicial é definido como `null`
const [connectedAddress, setConnectedAddress] = useState(null);
// gestão do estado do provedor, signatário e contrato
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [contract, setContract] = useState(null);
return (
<div>
<div>
<div>
<h1>Welcome to the Whitelist Tutorial!</h1>
<div>
<button onClick={connectWalletHandler}>
{connectButtonText}
</button>
<p> Connected Address : {connectedAddress} </p>
</div>
<div>
<button onClick={getNumWhitelistHandler}>
Get the number of whitelisted addresses
</button>
{numofWhitelisted} have already joined the Whitelist
</div>
<div>
<button onClick={joinWhitelistHandler}>
{joinWhiteListText}
</button>
</div>
</div>
</div>
<footer>Made by Alex Kumancev</footer>
</div>
);
O que são Provedores e Signatários?
- Um provedor é uma abstração de uma conexão de rede Ethereum que fornece uma interface clara e consistente para funções regulares de nós Ethereum.
- Um Signatário em ethers é uma abstração de uma Conta Ethereum que pode ser usada para assinar mensagens e transações e enviar transações assinadas à Rede Ethereum para realizar mudanças de estado.
// a declação if-else verifica se o navegador possui Metamasks instaladas
// se instalado, ele se conectará à primeira conta/endereço
const connectWalletHandler = () => {
if (window.ethereum) {
window.ethereum
.request({ method: "eth_requestAccounts" })
.then((result) => {
// após a conexão, o estado de connectAddress e connectButtonText será atualizado
setConnectedAddress(result[0]);
setConnetButtonText("Wallet Connected!");
// updateEthers iniciará o provedor, o signatário e a instância do contrato
updateEthers();
});
} else {
setConnectedAddress("Please Install Metamask Extension!");
}
};
const updateEthers = async () => {
// iniciar um provedor com ethers.js
const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider);
// iniciar um signatário com ethers.js
const signer = provider.getSigner();
setSigner(signer);
// iniciando uma nova instância de contrato com ethers.js
const contract = new ethers.Contract(contractAddress, contract_abi, signer);
setContract(contract);
// registrará o provedor, o assinante e o contrato no console quando for bem-sucedido.
console.log({provider, signer , contract})
};
Obtendo o número de contas na lista de permissões e ingressando na lista de permissões
const getNumWhitelistHandler = async () => {
// chama o uint público numAddressesWhitelisted de nosso contrato diretamente
let number = await contract.numAddressesWhitelisted.length;
setNumofWhitelisted(number);
};
const joinWhitelistHandler = async () => {
// chama a função addAddressToWhitelist em nosso contrato inteligente diretamente
const tx = await contract.addAddressToWhitelist();
await tx.wait();
setJoinWhiteListText("Succesfully Joined!");
await getNumWhitelistHandler();
};
Juntando o front-end
import { useState } from "react";
import { ethers } from "ethers";
import "./styles.css";
import contract_abi from "./contract_abi.json";
export default function Whitelist() {
const contractAddress = `0x90b989349A58a20415Cb3ff440b6244cF3737e12`;
const [numofWhitelisted, setNumofWhitelisted] = useState(null);
const [connectButtonText, setConnetButtonText] = useState("Connect Wallet");
const [connectedAddress, setConnectedAddress] = useState(null);
// gestão do estado do provedor, signatário e contrato
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [contract, setContract] = useState(null);
const connectWalletHandler = () => {
if (window.ethereum) {
window.ethereum
.request({ method: "eth_requestAccounts" })
.then((result) => {
setConnectedAddress(result[0]);
setConnetButtonText("Wallet Connected!");
updateEthers();
});
} else {
setConnectedAddress("Please Install Metamask Extension!");
}
};
const updateEthers = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider);
console.log(provider.getCode(contractAddress));
const signer = provider.getSigner();
setSigner(signer);
const contract = new ethers.Contract(contractAddress, contract_abi, signer);
setContract(contract);
};
const getNumWhitelistHandler = async () => {
let number = await contract.numAddressesWhitelisted.length;
setNumofWhitelisted(number);
};
const joinWhitelistHandler = async () => {
const tx = await contract.addAddressToWhitelist();
await tx.wait();
setJoinWhiteListText("Succesfully Joined!");
await getNumWhitelistHandler();
};
return (
<div>
<div className="main">
<div>
<h1 className="title">Welcome to the Whitelist Tutorial!</h1>
<div>
<button onClick={connectWalletHandler}>
{connectButtonText}
</button>
<p> Connected Address : {connectedAddress} </p>
</div>
<div className="description">
<button onClick={getNumWhitelistHandler}>
// Obter o número de endereços permitidos
</button>
{numofWhitelisted} have already joined the Whitelist
</div>
<div>
<button onClick={joinWhitelistHandler}>
{joinWhiteListText}
</button>
</div>
</div>
</div>
<footer className="footer">Made by Alex Kumancev</footer>
</div>
);
}
Antes de conectar à Metamask
Depois de conectar à Metamask
Antes de você sair
Com um maior entendimento do React, você notará que existem várias práticas recomendadas, como Custom Hooks, ContextAPI e o hook useReducer
que podem ser usados para tornar nosso código mais limpo e eficiente.
Quais são alguns dos recursos que você pode implementar por conta própria?
Criei um repositório para esta lição, com uma pasta separada para o contrato inteligente e o frontend. Você pode olhar para ele.
Top comments (0)