Autenticação/Login em dapp Web3 com MetaMask – LuizTools
Escrito por Luiz Duarte em 17/05/2022
Não sei se você percebeu mas estamos vivendo uma nova revolução na Internet.
A primeira grande revolução foi a criação da própria web como conhecemos, a 1.0, com páginas estáticas nos anos 80 e 90, onde os usuários eram apenas consumidores de conteúdo. Depois tivemos a Web 2.0 nos anos 2000, com as redes sociais e big techs à frente desta revolução e os usuários se tornando produtores de conteúdo.
Agora estamos entrando na Web 3.0, onde a blockchain, as criptomoedas e as carteiras virtuais estão decentralizando o acesso à rede, devolvendo a nossa privacidade, aumentando as interações P2P e muito mais. A web dos dapps (decentralized applications).
Se você já sabe programar para a web 2.0 vai aproveitar muito do seu conhecimento em webapps para a criação dos seus dapps. Por exemplo, o HTML, CSS e JS client-side não mudam em nada. No entanto, a interação com a blockchain e a integração com carteiras mudam bastante a dinâmica que estamos acostumados.
Neste tutorial, quero lhe mostrar como fazer um fluxo de login usando ReactJS (embora você possa ignorar esta parte e usar outra lib de frontend qualquer) e a carteira MetaMask, uma das mais populares do mercado. Caso não saiba o que é MetaMask e/ou ainda não possua uma (é gratuito), assista ao vídeo abaixo.
Independente da MetaMask, você consegue aproveitar o que vou ensinar aqui para integração com qualquer uma dessas carteiras instaladas em browsers que sigam como padrão ERC20, que é o usado na rede Ethereum, BSC e outras. E caso prefira assistir a um aulão ao invés de seguir este tutorial em texto, pode assistir ao vídeo abaixo.
Vou partir do pressuposto então que você já tem uma carteira MetaMask criada e configurada no seu navegador e vou partir para o setup do nosso projeto. Você vai precisar do Node.js instalado na sua máquina (disponível em nodejs.org) e este não é necessariamente o melhor tutorial para quem nunca usou ReactJS na vida, a lib de frontend que vou usar para construção das páginas. Não tem nada muito elaborado e vou fazer bem passo a passo, mas se quiser realmente entender React, recomendo este outro tutorial primeiro.
Dito isso, para criar o seu projeto use o seguinte comando no terminal.
npx create-react-app metamask-login
Vai demorar um bocado dependendo da sua máquina e da sua Internet, não se assuste. Depois que finalizar, entre na pasta do projeto e rode o comando abaixo para instalar algumas dependências que vamos precisar, principalmente a lib EthersJS, responsável por facilitar a integração com a MetaMask.
Agora que já temos o projeto criado e dependências instaladas, rode ele com o comando abaixo para ver se está funcionando corretamente.
Após ver o resultado no navegador (localhost:3000), volte ao seu projeto e vamos criar o HTML da página que é a parte mais fácil. Para fazer isso, abra seu App.js e procure pela instrução return, que retorna o HTML a ser renderizado. Construa um HTML como abaixo.
return ( <div className="App"> <header className="App-header"> <h1>Login</h1> <div> <button onClick={doSignIn}> Sign In with MetaMask </button> <button onClick={doSignUp}> Sign Up with MetaMask </button> </div> </header> </div> );
<button onClick={doSignIn}>
<button onClick={doSignUp}>
Adicionei aqui dos botões, um de sign in (para quem já tem cadastro na aplicação) e outro de sign up (para quem não tem cadastro). Em dapps "raiz" (puros) o mais comum é ter apenas um botão, mas mais à frente vai ficar mais claro porque optei por ter dois. Repare também que os botões possuem uma propriedade onClick que referenciam funções que ainda não temos. Crie eles antes do return, vazias mesmo ou com um console.log ou alert, apenas para testar os botões.
async function doSignIn(){ } async function doSignUp(){ } return ...
async function doSignIn(){ }
async function doSignUp(){ }
Agora é hora de começar a programar a funcionalidade de integração.
DApp "raiz"
Numa aplicação web geralmente temos um backend, certo? No caso de dapps "raiz" (puros) você sempre terá uma blockchain como seu backend e se usaremos MetaMask então essa blockchain será a Ethereum ou outra compatível, como a Binance Smart Chain (BSC), que eu costumo usar por ter taxas menores.
Resumindo: nosso front React se conectará diretamente na blockchain usando a carteira MetaMask como "provedor de conexão".
Então vamos fazer isso tudo na nossa função doSignUp, que criamos anteriormente e que é chamado quando o botão de signup é clicado. Se você só quer fazer com um botão, pode fazer também, mas esta abordagem que vou propor tem o seu mérito, então confia. 🙂
A primeira coisa que temos que programar na doSignUp é a verificação se o usuário possui carteira MetaMask no navegador. Fazemos isso verificando se existe uma propriedade ethereum na janela, que é injetada quando a MetaMask está instalada.
import {useState} from 'react'; function App() { const [error, setError] = useState(''); async function doSignUp() { setError(''); if (!window.ethereum) return setError(`No MetaMask found!`); } ...
async function doSignUp() {
if (!window.ethereum) return setError(`No MetaMask found!`);
Repare que eu adicionei além da verificação um state de erro, para exibir mensagens de erro que vamos lançar ao usuário eventualmente. Você pode posicionar este state para renderização onde quiser no seu HTML, como abaixo.
... { error ? <p>{error}</p> : <></> } </div> </header> </div>
error ? <p>{error}</p> : <></>
Experimente agora acessar a sua aplicação (localhost:3000) a partir de uma navegador que não tem MetaMask instalada e clique no botão, verá que o erro vai aparecer.
Mas voltando à função doSignUp, vamos solicitar ao usuário que nos conceda acesso às informações básicas da sua carteira. Para fazer isso precisamos primeiro importar a biblioteca ethers no topo do nosso App.js.
Depois disso, na função doSignUp vamos carregar o objeto window.ethereum como sendo um novo provedor Web3, ou seja, uma conexão com a blockchain. Através desta conexão podemos enviar comandos (send) como o eth_requestAccounts que pede a permissão para ler as informações da carteira conectada, como mostra o código abaixo.
async function doSignUp(){ setError(''); if (!window.ethereum) return setError(`No MetaMask found!`); try { const provider = new ethers.providers.Web3Provider(window.ethereum); const accounts = await provider.send("eth_requestAccounts", []); if (!accounts || !accounts.length) return setError('Wallet not found/allowed!'); }catch(err){ setError(err.message); } }
async function doSignUp(){
const accounts = await provider.send("eth_requestAccounts", []);
if (!accounts || !accounts.length) return setError('Wallet not found/allowed!');
Se o usuário der permissão (a MetaMask vai avisar ele no exato instante que você enviar o comando), o objeto accounts vai conter um array com ao menos uma conta permitida. Agora, caso o usuário não autorize, o array accounts vai estar vazio e vamos novamente exibir um erro. E se o usuário cancelar o pedido, vai disparar um erro que estamos capturando com try/catch.
Recomendo que teste e quando aparecer a janela do MetaMask, cancele o pedido de permissão para ver o erro acontecendo ou apenas desmarque a carteira que foi pedida permissão.
Agora para finalizar o fluxo com sucesso, se o usuário concedeu permissão você terá acesso a informação da carteira que está autenticada no browser e com isso seu login estará completo na blockchain. Vou adicionar a informação da carteira no localStorage e também em um state.
const [wallet, setWallet] = useState(''); async function doSignUp() { setError(''); if (!window.ethereum) return setError(`No MetaMask found!`); const provider = new ethers.providers.Web3Provider(window.ethereum); const accounts = await provider.send("eth_requestAccounts", []); if (!accounts || !accounts.length) return setError('Wallet not found/allowed!'); localStorage.setItem('wallet', accounts[0]); setWallet(accounts[0]); }
const [wallet, setWallet] = useState('');
localStorage.setItem('wallet', accounts[0]);
setWallet(accounts[0]);
A adição no localStorage permite que no futuro, quando o usuário voltar, você saiba quem ele é e não precise pedir que ele se autentique novamente.
Já a adição no state wallet permite que você faça o seu frontend React reagir ao login e se modificar, como abaixo, em que mostro outra div de usuário já autenticado, mas que poderia ser um redirecionamento de tela por exemplo. De qualquer forma, seu usuário está logado com a carteira dele.
return ( <div className="App"> <header className="App-header"> <h1>Login</h1> <div> { !wallet ? ( <> <button onClick={doSignIn}> Sign In with MetaMask </button> <button onClick={doSignUp}> Sign Up with MetaMask </button> </> ) : ( <> <p> Wallet: {wallet} </p> <button onClick={doLogout}> Logout </button> </> ) } { error ? <p>{error}</p> : <></> } </div> </header> </div> );
Wallet: {wallet}
<button onClick={doLogout}>
Repare que deixei ali um botão de logout associado a uma função doLogout que você pode criar e que simplesmente vai limpar o localStorage e o state.
function doLogout(){ localStorage.removeItem('wallet'); setWallet(''); setError(''); }
localStorage.removeItem('wallet');
setWallet('');
E agora recomendo que você teste novamente a autenticação e verá que a tela muda quando o usuário se autentica e aparece a informação da carteira dele. E se você fizer logout, tudo volta para o início.
Com o usuário autenticado, você pode interagir com smart contracts, ver saldo da carteira, transferir fundos e muito mais. Dá uma olhada neste tutorial aqui para continuar os estudos.
E se quiser fazer o comportamento de quando o usuário voltar ele já estar logado, pode usar um useEffect para isso.
import {useState, useEffect} from 'react'; function App(){ useEffect(() => { const address = localStorage.getItem('wallet'); setWallet(address); if (address) doSignIn(); }, []) ...
const address = localStorage.getItem('wallet');
setWallet(address);
if (address) doSignIn();
Ele vai disparar assim que a página for carregada, vai verificar se tem carteira salva no localStorage e avisar o state de wallet, fazendo a aplicação entender que aquela carteira está autenticada. Repare que chamei a função doSignIn ali no final, que na prática não faz nada hoje. Ela será importante a seguir no nosso tutorial, que continua neste link!