Creare un Front End con Ethers.js e vanilla Javascript e connettersi ad Hardhat con Metamask

03 gennaio 2022

Nelle lezioni precedenti abbiamo iniziato a vedere come inizializzare uno Smart Contract di esempio che servità come nostro backend. Ora possiamo iniziare a creare il nostro Front End che si collegherà alla Blockchain di Ethereum (per il momento sempre in locale con Hardhat) per interagirvi. La maggiorparte delle guide che si trovano sul web fanno riferimento al framework React.js. Per mantenere le cose semplici e capire esattamente cosa succede, in questa lezione utilizzeremo Javascript puro (vanilla).

Prima però sistemiamo un po' di configurazione.

Configurare Hardhat per MetaMask

Attualmente MetaMask ha un problema con Hardhat e non funziona con la configurazione di default. In attesa che il problema venga risolto, dobbiamo modificare leggermente la configurazione di Hardhat. La configurazione di Hardhat si trova nel file hardhat.config.js all'interno della cartella del nostro progetto creato in precedenza.

All'interno dei module.exports = {} dovremo aggiungere questa proprietà:

networks: {
  hardhat: {
    chainId: 1337
  },
}

Il risultato dovrebbe essere:

hardhat.config.js
[...]
module.exports = {
  solidity: "0.8.4",
  networks: {
    hardhat: {
      chainId: 1337
    },
  }
};

Adesso possiamo far ripartire il nodo locale di Hardhat. Se hai ancora aperto quello lancoato nella lezione precedente chiudilo e riaprilo.

npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========

WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.

Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

[...]

Ora possiamo collegare Metamask alla nostra rete in locale. Premi sull'icona di Metamask (la testa di una volpe, se non la vedi potresti doverla cercare fra le estensioni del browser) e nel menu con le reti scegli Localhost 8545. Se non la vedi probabilmente devi premere su "Show/hide test networks" ed abilitare le reti di test.

Creazione del Front End

Una nota importante: Metamask non si collega con la nostra pagina web se la apriamo direttamente dal file system, dobbiamo invece aprirla da un web server. Possiamo creare facilmente un web server in locale utilizzando il tool http-server in nodejs. Per installarlo lanciamo questo comando:

npm install http-server -g

Questo ci permetterà di servire tramite server web il contenuto di qualsiasi cartella in locale.

Bene, fatte queste configurazioni iniziali possiamo iniziare a creare la nostra interfaccia web!

Creiamo una nuovo cartella, ad esempio test-frontend, e iniziamo a creare la nostra pagina di base.

index.html
<!DOCTYPE html>
<html lang="it">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Saluti da Ethereum!</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  </head>
  <body>
    <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>

    <div class="container text-center bg-light">
      <h1 class="text-primary">Saluti da Ethereum!</h1>
    </div>

    <script src="saluti.js"></script>
  </body>
</html>

Questo è un semplice template che include Bootstrap (per sistemare un po' la grafica) e la libreria Ethers.js. Saluti.js è il file che conterrà il nostro codice. Ethers.js è la libreria che ci permetterà di collegarci ed interagire con Ethereum.

Metamask inietta all'interno del browser la libreria "ethereum". La prima cosa che dobbiamo fare quindi è verificare se "windows.ethereum" è definita o no.

Apriamo il file saòluti.js ed aggiungiamo questo codice:

saluti.js
function verificaMetamask() {
  return window.ethereum
}

if (!verificaMetamask()) {
  console.log('Ethereum non trovato!')
} else {
  console.log('Abbiamo Ethereum!')
}

Ora possiamo lanciare http-server all'interno della cartella contenente i file (test-frontend).

http-server

Starting up http-server, serving ./

[...]

Available on:
  http://127.0.0.1:8080
  http://192.168.0.106:8080
Hit CTRL-C to stop the server

Come vediamo dall'output, possiamo accedere alla nostra pagina a questo indirizzo: http://127.0.0.1:8080

Se apriamo la pagina in un browser su cui abbiamo installato Metamask e apriamo la console dovremmo leggere:

Abbiamo Ethereum!

Se invece la apriamo in un browser senza Metamask dovremmo leggere:

Ethereum non trovato!

Dopo che abbiamo verificato la connessione ad Ethereum abbiamo bisogno di un account. Modifichiamo il codice in questo modo questa funzione nel file js:

saluti.js
if (!window.ethereum) {
  console.log('Ethereum non trovato!')
} else {
  console.log('Abbiamo Ethereum!')

  ethereum.request({ method: 'eth_accounts'})
    .then( accounts => console.log('Account: ', accounts) )
    .catch( error => console.log(error) )
}

Adesso se proviamo a ricaricare la pagina dovremmo ottenere questo output nella console:

Abbiamo Ethereum!
Account:  []

Nel Web3, per poter accedere agli account occorre che l'utente abbia esplicitamente "connesso" il proprio Wallet all'applicazione. Connettere il proprio Wallet assume il ruolo del log-in nelle tradizionali applicazioni web.

Per permettere all'utente di connettere il proprio Wallet dobbiamo aggiungere un pulsante e collegarci una funzione che richiede all'utente di collegare il proprio Wallet. Approfittiamone per fare un po' di refactoring e mostrare sulla pagina web i messaggi che visualizzavamo sulla console.

index.html
<!DOCTYPE html>
<html lang="it">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Saluti da Ethereum!</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  </head>
  <body>
    <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>

    <div class="container text-center bg-light">
      <h1 class="text-primary">Saluti da Ethereum!</h1>

      <div id="messaggi"></div>
      <button id="connectButton" style="display: none" onClick="connect()" type="button" class="btn btn-primary">Connetti il tuo Wallet</button>

    </div>

    <script src="saluti.js"></script>
  </body>
</html>

Abbiamo aggiunto un div per visualizzare i messaggi ed un bottone per connettere il proprio account. Veniamo al codice javascript:

saluti.js
const messaggiDiv = document.getElementById('messaggi')
const connectButton = document.getElementById('connectButton')

let accountCollegato = null

function visualizzaLogin() {
  messaggiDiv.innerHTML = '<p class="alert alert-primary">Connetti il tuo Account!</p>'
  connectButton.style.display = "block"
}

function nascondiLogin() {
  messaggiDiv.innerHTML = ''
  connectButton.style.display = "none"
}

Qua inizializziamo alcuni puntatori agli elementi della pagina e l'account dell'utente. Poi abbiamo creato due semplici funzioni per nascondere o visualizzare il bottone in base allo stato dell'utente.

saluti.js
function collegaAccount(accounts) {
  accountCollegato = accounts[0]
  console.log('Account: ', accountCollegato)
  messaggiDiv.innerHTML = '<p class="alert alert-success">Benvenuto ' + accountCollegato + '!</p>'
}

Questa funzione riceve la lista degli account collegati (ce ne può essere più di uno) e prende il primo disponibile. Poi visualizza un messaggio di benvenuto con l'indirizzo dell'account collegato.

saluti.js
function gestisciAccount(accounts) {
  if (accounts.length === 0) {
    console.log('Nessun account collegato.');
    visualizzaLogin()
  } else if (accounts[0] !== accountCollegato) {
    nascondiLogin()
    collegaAccount(accounts)
  }
}

Questa funzione reagisce ai cambiamenti degli account (è possibile disconnettere l'account o connetterne altri) e modifica l'interfaccia di conseguenza.

saluti.js
function connect() {
  ethereum
    .request({ method: 'eth_requestAccounts' })
    .then(gestisciAccount)
    .catch((err) => {
      if (err.code === 4001) {
        console.log('Connessione rifiutata!');
        messaggiDiv.innerHTML = '<p class="alert alert-danger">Connetti il tuo Wallet!</p>'
      } else {
        console.error(err);
      }
    });
}

Questa funzione è quella collegata al bottone per il collegamento dell'Account. In caso di successo viene chiamata la funzione gestisciAccount per associare l'account dell'utente. Da notare che viene gestita l'eventualità in cui un utente cancelli il collegamento dopo aver cliccato sul bottone.

Veniamo ora alla parte di gestione della logica del tutto:

saluti.js
if (!window.ethereum) {
  console.log('Ethereum non trovato!')
  if (messaggiDiv) {
    messaggiDiv.innerHTML = '<p class="alert alert-danger">Ethereum non trovato! Installa Metamask!</p>'
  }
} else {
  console.log('Abbiamo Ethereum!')

  ethereum.on('accountsChanged', gestisciAccount);

  ethereum.request({ method: 'eth_accounts'})
    .then(
      accounts => {
        console.log('Account: ', accounts)
        if (accounts.length < 1) {
          visualizzaLogin()
        } else {
          collegaAccount(accounts)
        }
      }
    )
    .catch( error => {
      console.log(error)
    })
}

In primo luogo viene verificato se Metamask è presente. In caso positivo, viene ivviato un listener per l'evento di accountsChanged, che resta in ascolto per i cambiamenti degli account collegati (es: l'account viene scollegato) e nel caso richiama la funzione gestisciAccount. Successivamente viene richiesta la lista degli account già collegati: se ne viene trovato qualcuno si prosegue altrimenti viene visualizzata l'interfaccia di login (il bottone).

Nella prossima lezione richiameremo finalmente le funzioni dello Smart Contract!