Utilizzare Ethers.js per connettersi ad Hardhat

02 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 un client che si collegherà alla Blockchain di Ethereum (per il momento sempre in locale con Hardhat) per interagirvi. Per il il momento inizieremo a collegarci da console per testare il funzionamento senza complicare troppo le cose, nella prossima lezione integreremo anche il Front End web.

Innanzitutto dobbiamo tornare nell cartella del progetto Hardhat (test-hardhat) per lanciare il nostro nodo locale:

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

[...]

L'indirizzo dal quale la nostra blockchain sarà accessibile è http://127.0.0.1:8545/

Hardhat ci fornisce in automatico 20 account (wallet) che potremo utilizzare per i nostri test. Questo ci sarà molto utile perchè i Wallet sono precaricati con 1000 ETH (purtroppo sono ETH che valgono solo nella nostra rete locale! ;) )

Adesso possiamo lanciare lo Smart Contract nella nostra rete locale utilizzando questo comando (in una nuova shell):

npx hardhat run scripts/sample-script.js --network localhost

Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Come abbiamo già visto, quella stringa esadecimale (0x5FbDB2315678afecb367f032d93F642f64180aa3) rappresenta l'indirizzo a cui è stato caricato il nostro Smart Contract.

Adesso possiamo tornare nella nostra cartella di base e iniziare a creare un nuovo progetto nodejs:

mkdir test-ethers
cd test-ethers
npm init -y
npm install --save ethers

Abbiamo appena installato la libreria Ethers che ci consentirà di collegarci con la Blockchain. Adesso possiamo creare un nuovo file test.js e ci inseriamo questo codice:

test.js
const { ethers } = require("ethers")

Questo ci serve ad includere la libreria. Continuamo:

test.js
const provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545')

Questo ci permette di collegarci alla nostra Blockchain in locale fornita da Hardhat, all'indirizzo che abbiamo visto poco fa. Secondo la documentazione di Ethers.js se ci colleghiamo in locale non ci sarebbe bisogno di specificare l'indirizzo, tuttavia se non lo faccio ottengo poi degli errori.

Per "leggere" da Blockchain Ethereum non è utilizzare un Wallet con valuta. Possiamo facilmente verificarlo perchè per il momento non utilizzeremo proprio un Wallet.

test.js
const signer = provider.getSigner()
console.log('Signer: ', signer)

Un signer (in generale) rappresenta un Wallet. Proviamo ad eseguire questo codice e vediamo cosa succede:

node test.js

Signer:  JsonRpcSigner {
  _isSigner: true,
  provider: JsonRpcProvider {
    _isProvider: true,
    _events: [],
    _emitted: { block: -2 },
    formatter: Formatter { formats: [Object] },
    anyNetwork: false,
    _networkPromise: Promise { <pending> },
    _maxInternalBlockNumber: -1024,
    _lastBlockNumber: -2,
    _pollingInterval: 4000,
    _fastQueryDate: 0,
    connection: { url: 'http://127.0.0.1:8545' },
    _nextId: 42
  },
  _index: 0,
  _address: null
}

Come possiamo vedere l'address di questo signer è null, quindi non è associato a nessun Wallet.

Proviamo adesso ad aggiungere un'interrogazione verso la Blockchain per vedere cosa succede. Ad esempio possiamo provare a verificare se riusciamo ad ottentere il bilancio di uno degli account che ci sono stati inizializzati da Hardhat quando abbiamo lanciato il nodo locale. Aggiungiamo queste linee (modificate eventualmente l'indirizzo con uno di quelli creati da Hardhat):

test.js
provider.getBalance("0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199")
.then(
  balance => console.log('Balance: ', ethers.utils.formatEther(balance))
)
.catch(
  error => console.log('Error retrieving balance: ', error)
)

Lanciamo nuovamente lo script:

node test.js

Signer: [...]
Balance:  10000.0

Siamo riusciti ad interrogare la Blockchain!

Bene, adesso possiamo commentare le linee precedenti che non ci servono e provare ad interagire con il nostro Smart Contract. Per fare questo ci servono 2 informazioni:

  • L'indirizzo a cui è disponibile lo Smart Contract (nel mio caso: 0x5FbDB2315678afecb367f032d93F642f64180aa3)
  • Le informazioni di interfaccia ABI generate da Hardhat durante la compilazione.

Come già detto queste informazioni si trovano all'interno del file test-hardhat/artifacts/contracts/Greeter.sol/Greeter.json Non ci serve tutto il file, solo il contenuto del parametro "abi".

Aggiungiamo queste due informazioni all'interno del nostro script:

test.js
const greeterAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
const greeterAbi = [
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "_greeting",
        "type": "string"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "greet",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "_greeting",
        "type": "string"
      }
    ],
    "name": "setGreeting",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Adesso possiamo istanziare il nostro Smart Contract:

test.js
const greeterContract = new ethers.Contract(greeterAddress, greeterAbi, provider);

Ora siamo pronti per interagire con il nostro Smart Contract!

test.js
greeterContract.greet()
.then(
  greet => console.log('Ethereum dice: ', greet)
).catch(
  error => console.log('Errore durante la chiamata a greet():', error)
)
node test.js

Ethereum dice:  Hello, Hardhat!

Proviamo ora a richiamare il metodo per impostare un nuovo messaggio:

test.js
greeterContract.setGreeting('Ciao Mondo!')
.then(
  () => console.log('Nuovo saluto salvato!')
).catch(
  error => console.log('Errore durante la chiamata a setGreeting():', error)
)
node test.js

Errore durante la chiamata a setGreeting(): Error: sending a transaction requires a signer (operation="sendTransaction", code=UNSUPPORTED_OPERATION, version=contracts/5.5.0)
    [...]
  reason: 'sending a transaction requires a signer',
  code: 'UNSUPPORTED_OPERATION',
  operation: 'sendTransaction'

In questo caso otteniamo un errore perchè per scrivere sulla Blockchain occorre utilizzare un Signer (=Wallet). La sintassi corretta è la seguente:

test.js
const greeterContractWithSigner = greeterContract.connect(signer);

greeterContractWithSigner.setGreeting('Ciao Mondo!')
.then(
  () => console.log('Nuovo saluto salvato!')
).catch(
  error => console.log('Errore durante la chiamata a setGreeting():', error)
)
node test.js

Ethereum dice:  Hello, Hardhat!
Nuovo saluto salvato!

Adesso non riceviamo più un errore! Di norma però ci servirebbe un Wallet con un po' di ETH per poter pagare le commissioni alla rete. Proviamo a chiamare nuovamente lo script per verificare se il messaggio è stato salvato veramente:

node test.js

Ethereum dice:  Ciao Mondo!
Nuovo saluto salvato!

A quanto pare ha funzionato!

Nella prossima lezione integreremo le stesse funzionalità su un'interfaccia web.