Vai al contenuto principale

Contratto intelligente "Hello World" per principianti - full stack

solidityhardhatalchemycontratti intelligentidistribuzioneblockexplorerfrontendtransazioni
Principiante
nstrike2
25 ottobre 2021
45 minuti letti minute read

Questa guida fa per te se hai appena iniziato con lo sviluppo sulla blockchain e non sai da dove cominciare o come distribuire e interagire con i contratti intelligenti. Esamineremo la creazione e la distribuzione di un semplice contratto intelligente sulla rete di prova di Goerli, utilizzando MetaMask(opens in a new tab), Solidity(opens in a new tab), Hardhat(opens in a new tab) e Alchemy(opens in a new tab).

Per completare questo tutorial avrai bisogno di un conto di Alchemy. Registrati per un conto gratuito(opens in a new tab).

Se in qualsiasi momento hai domande, non esitare a contattarci nel Discord di Alchemy(opens in a new tab)!

Parte 1- Crea e distribuisci il tuo contratto intelligente usando Hardhat

Connettersi alla rete di Ethereum

Esistono molti modi per effettuare richieste alla catena di Ethereum. Per semplicità, ci serviremo di un conto gratuito su Alchemy, una piattaforma per sviluppatori di blockchain e API che ci consentirà di comunicare con la catena di Ethereum senza dover eseguire noi stessi un nodo. Alchemy offre anche strumenti di monitoraggio e analisi per gli sviluppatori di cui ci serviremo in questo tutorial per comprendere al meglio l'andamento della distribuzione del nostro contratto intelligente.

Crea la tua app e la chiave API

Una volta creato un conto di Alchemy, puoi generare una chiave API creando un'app. Questo ti consentirà di effettuare richieste alla rete di prova di Goerli. Se non hai familiarità con le reti di prova puoi leggere la guida di Alchemy alla scelta di una rete(opens in a new tab).

Sul pannello di controllo di Alchemy, trova il menu a discesa delle App nella barra di navigazione e fai clic su su Crea App.

Creare l'app Hello world

Nomina la tua app 'Hello World' e scrivi una breve descrizione. Seleziona Staging come tuo ambiente e Goerli come tua rete.

Vista della creazione dell'app Hello world

Nota: assicurati di selezionare Goerli, altrimenti questo tutorial non funzionerà.

Fa i clic su Crea app. La tua app apparirà nella tabella sottostante.

Crea un conto di Ethereum

Per inviare e ricevere transazioni, hai bisogno di un account di Ethereum. Useremo MetaMask, un portafoglio virtuale nel browser che permette agli utenti di gestire l'indirizzo del proprio conto di Ethereum.

Puoi scaricare e creare gratuitamente un conto di MetaMask qui(opens in a new tab). Quando crei un account, o se ne possiedi già uno, assicurati di passare alla "rete di prova di Goerli" in alto a destra (così da non avere a che fare con denaro reale).

Fase 4: aggiungi ether da un Faucet

Per distribuire il tuo contratto intelligente sulla rete di prova, avrai bisogno di alcuni ETH finti. Per ottenere ETH sulla rete di Goerli, vai ad un faucet di Goerli e immetti l'indirizzo del tuo conto di Goerli. Si noti che i faucet di Goerli ultimamente possono essere un po' inaffidabili - fare riferimento alla pagina delle reti di prova per un elenco delle opzioni da provare:

Nota: a causa della congestione della rete, questa operazione potrebbe richiedere del tempo ``

Fase 5: controlla il saldo

Per ricontrollare che gli ETH siano nel tuo portafoglio, facciamo una richiesta eth_getBalance(opens in a new tab) usando lo strumento compositore di Alchemy(opens in a new tab). Questo restituirà l'importo di ETH nel nostro portafoglio. Per saperne di più consultate il breve tutorial di Alchemy su come usare lo strumento compositore(opens in a new tab).

Inserisci l'indirizzo del tuo conto di MetaMask e fai clic su Invia richiesta. Vedrai una risposta simile al pezzetto di codice qui sotto.

1{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }
Copia

Nota: questo risultato è in wei, non in ETH. Wei è usato come taglio più piccolo dell'ether.

Meno male! I nostri soldi finti ci sono tutti.

Fase 6: inizializza il progetto

Per prima cosa dobbiamo creare una cartella per il nostro progetto. Passa alla tua riga di comando e inserisci quanto segue.

1mkdir hello-world
2cd hello-world

Ora che siamo nella cartella del nostro progetto, useremo npm init per inizializzare il progetto.

Se non hai ancora installato npm, segui queste istruzioni su come installare Node.js e npm(opens in a new tab).

Ai fini di questo tutorial, non ha importanza a come rispondi alle domande di inizializzazione. Per tuo riferimento, ecco come abbiamo fatto:

1package name: (hello-world)
2version: (1.0.0)
3description: hello world smart contract
4entry point: (index.js)
5test command:
6git repository:
7keywords:
8author:
9license: (ISC)
10
11About to write to /Users/.../.../.../hello-world/package.json:
12
13{
14 "name": "hello-world",
15 "version": "1.0.0",
16 "description": "hello world smart contract",
17 "main": "index.js",
18 "scripts": {
19 "test": "echo \"Error: no test specified\" && exit 1"
20 },
21 "author": "",
22 "license": "ISC"
23}
Mostra tutto

Approva il package.json e siamo pronti!

Passo 7: scarica Hardhat

Hardhat è un ambiente di sviluppo per compilare, distribuire, testare ed effettuare il debug del tuo software di Ethereum. Aiuta gli sviluppatori nella costruzione di contratti intelligenti e dapp localmente, prima di distribuirli alla catena.

Nel nostro progetto hello-world esegui:

1npm install --save-dev hardhat

Dai un'occhiata a questa pagina per ulteriori dettagli sulle istruzioni d'installazione(opens in a new tab).

Fase 8: crea un progetto Hardhat

All'interno della cartella di progetto hello-world esegui:

1npx hardhat

Dovresti poi vedere un messaggio di benvenuto e l'opzione per selezionare cosa desideri fare. Seleziona “crea un hardhat.config.js vuoto”:

1888 888 888 888 888
2888 888 888 888 888
3888 888 888 888 888
48888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
5888 888 "88b 888P" d88" 888 888 "88b "88b 888
6888 888 .d888888 888 888 888 888 888 .d888888 888
7888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
8888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
9
10👷 Welcome to Hardhat v2.0.11 👷‍
11
12What do you want to do? …
13Create a sample project
14❯ Create an empty hardhat.config.js
15Quit
Mostra tutto

Questo genererà un file hardhat.config.js nel progetto. Lo utilizzeremo più avanti nel tutorial per specificare la configurazione del nostro progetto.

Fase 9: aggiungi le cartelle del progetto

Per mantenere organizzato il nostro progetto, creiamo due nuove cartelle. Nella riga di comando, vai alla cartella di root del tuo progetto hello-world e digita:

1mkdir contracts
2mkdir scripts
  • contracts/ è dove manterremo il file del codice del nostro smart contract hello world
  • scripts/ è dove manterremo gli script per distribuire e interagire con il nostro contratto

Fase 10: compila il nostro contratto

Potresti chiederti: quando scriveremo del codice? È arrivato il momento!

Apri il progetto hello-world nel tuo editor preferito. La maggior parte dei contratti intelligenti è scritta in Solidity, che useremo per scrivere il nostro contratto intelligente

  1. Vai alla cartella contracts e crea un nuovo file chiamato HelloWorld.sol
  2. Di seguito, un esempio del contratto intelligente Hello World che utilizzeremo per questo tutorial. Copia il contenuto seguente in un file HelloWorld.sol.

Nota: assicurati di leggere i commenti per comprendere cosa fa questo contratto.

1// Specifica la versione di Solidity, utilizzando il controllo delle versioni semantico.
2// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragma solidity >=0.7.3;
4
5// Defines a contract named `HelloWorld`.
6// Un contratto è una raccolta di funzioni e dati (il suo stato). Una volta distribuito, un contratto risiede in un indirizzo specifico della blockchain Ethereum. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
7contract HelloWorld {
8
9 //Emitted when update function is called
10 //Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.
11 event UpdatedMessages(string oldStr, string newStr);
12
13 // Declares a state variable `message` of type `string`.
14 // Le variabili di stato sono variabili con valori memorizzati in modo permanente nello spazio di archiviazione (storage) del contratto. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.
15 string public message;
16
17 // Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.
18 // I costruttori sono utilizzati per inizializzare i dati del contratto. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
19 constructor(string memory initMessage) {
20
21 // Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).
22 message = initMessage;
23 }
24
25 // A public function that accepts a string argument and updates the `message` storage variable.
26 function update(string memory newMessage) public {
27 string memory oldMsg = message;
28 message = newMessage;
29 emit UpdatedMessages(oldMsg, newMessage);
30 }
31}
Mostra tutto

Questo è un contratto intelligente di base che memorizza un messaggio al momento della creazione. Può essere aggiornato richiamando la funzione update.

Fase 11: connetti MetaMask e Alchemy al tuo progetto

Abbiamo creato un portafoglio di MetaMask, un conto di Alchemy e scritto il nostro contratto intelligente; è arrivato il momento di collegarli.

Ogni transazione inviata dal tuo portafoglio richiede una firma tramite la tua chiave privata univoca. Per fornire al nostro programma quest'autorizzazione, possiamo memorizzare in sicurezza la nostra chiave privata in un file di ambiente. Qui memorizzeremo anche una chiave API per Alchemy.

Per saperne di più sull'invio delle transazioni, dai un'occhiata a questo tutorial(opens in a new tab) sull'invio di transazioni usando web3.

Prima, installa il pacchetto dotenv nella cartella del tuo progetto:

1npm install dotenv --save

Quindi, crea un file .env nella cartella di root del progetto. Aggiungi nel file la tua chiave privata di MetaMask e l'URL HTTP dell'API Alchemy.

Il tuo file di ambiente deve essere nominato .env o non verrà riconosciuto come un file di ambiente.

Non nominarlo process.env o .env-custom o in altro modo.

Il tuo .env dovrebbe somigliare a questo:

1API_URL = "https://eth-goerli.alchemyapi.io/v2/tua-chiave-api"
2PRIVATE_KEY = "tua-chiave-privata-metamask"

Per connetterli realmente al nostro codice, faremo riferimento a queste variabili nel nostro file hardhat.config.js nella fase 13.

Fase 12: installa Ethers.js

Ethers.js è una libreria che rende più facile interagire ed effettuare richieste a Ethereum tramite wrapping dei metodi JSON-RPC standard(opens in a new tab) con altri metodi più facili da usare.

Hardhat ci permette di integrare i plugin(opens in a new tab) per avere strumenti aggiuntivi e funzionalità estese. Sfrutteremo il plugin di Ethers(opens in a new tab) per la distribuzione del contratto.

Nella cartella del tuo progetto digita:

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

Fase 13: aggiorna hardhat.config.js

Finora abbiamo aggiunto diverse dipendenze e plugin, ora dobbiamo aggiornare hardhat.config.js in modo che il nostro progetto li riconosca tutti.

Aggiorna il tuo hardhat.config.js affinché somigli a questo:

1/**
2 * @type import('hardhat/config').HardhatUserConfig
3 */
4
5require("dotenv").config()
6require("@nomiclabs/hardhat-ethers")
7
8const { API_URL, PRIVATE_KEY } = process.env
9
10module.exports = {
11 solidity: "0.7.3",
12 defaultNetwork: "goerli",
13 networks: {
14 hardhat: {},
15 goerli: {
16 url: API_URL,
17 accounts: [`0x${PRIVATE_KEY}`],
18 },
19 },
20}
Mostra tutto

Fase 14: compila il contratto

Per assicurarti che tutto funzioni fino a questo punto, compila il contratto. L'attività di compilazione è una delle attività integrate di hardhat.

Dalla riga di comando esegui:

npx hardhat compile

Potresti ricevere un avviso SPDX license identifier not provided in source file, ma non ti preoccupare, si spera che tutto il resto funzioni! Altrimenti, puoi sempre inviare un messaggio nel Discord di Alchemy(opens in a new tab).

Fase 15: scrivi lo script di distribuzione

Ora che il nostro contratto è scritto e il nostro file di configurazione è pronto, è il momento di scrivere lo script di distribuzione del contratto.

Vai alla cartella script/ e crea un nuovo file chiamato deploy.js, aggiungendo i seguenti contenuti:

1async function main() {
2 const HelloWorld = await ethers.getContractFactory("HelloWorld")
3
4 // Start deployment, returning a promise that resolves to a contract object
5 const hello_world = await HelloWorld.deploy("Hello World!")
6 console.log("Contract deployed to address:", hello_world.address)
7}
8
9main()
10 .then(() => process.exit(0))
11 .catch((error) => {
12 console.error(error)
13 process.exit(1)
14 })
Mostra tutto

Nel suo tutorial sui Contratti(opens in a new tab) hardhat spiega in modo eccellente cosa fa ognuna di queste righe di codice nel loro, quindi riportiamo qui le loro spiegazioni.

1const HelloWorld = await ethers.getContractFactory("HelloWorld")

Un ContractFactory su ethers.js è un'astrazione usata per distribuire nuovi contratti intelligenti, quindi HelloWorld qui è una fabbrica(opens in a new tab) di istanze del nostro contratto hello world. Usando il plugin hardhat-ethers, le istanze ContractFactory e Contract sono connesse di default al primo firmatario (proprietario).

1const hello_world = await HelloWorld.deploy()

Chiamare deploy() su un ContractFactory avvierà la distribuzione e restituirà un Promise che si risolve in un oggetto Contract. Questo è l'oggetto che ha un metodo per ciascuna delle funzioni del nostro smart contract.

Fase 16: distribuisci il contratto

Siamo finalmente pronti a distribuire il nostro smart contract! Vai alla riga di comando ed esegui:

npx hardhat run scripts/deploy.js --network goerli

Vorrai poi vedere qualcosa del genere:

Contract deployed to address: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

Salva questo indirizzo. Lo useremo più avanti nel tutorial.

Se andiamo all'etherscan di Goerli(opens in a new tab) e cerchiamo l'indirizzo del nostro contratto, dovremmo poter vedere che è stato distribuito correttamente. La transazione somiglierà a questa:

L'indirizzo From dovrebbe corrispondere all'indirizzo del tuo conto di MetaMask mentre l'indirizzo To riporterà la dicitura Creazione del contratto. Se facciamo clic sulla transazione vedremo l'indirizzo del contratto nel campo To.

Congratulazioni! Hai appena distribuito un contratto intelligente su una rete di prova di Ethereum.

Per capire cosa sta succedendo, andiamo alla scheda Explorer nel nostro dashboard di Alchemy(opens in a new tab). Se hai diverse app di Alchemy assicurati di filtrare per app e selezionare Hello World.

Qui vedrai numerosi metodi JSON-RPC che Harhat/Ethers ha creato dietro le quinte per noi quando abbiamo chiamato la funzione .deploy(). Due metodi importanti sono eth_sendRawTransaction(opens in a new tab), che è la richiesta per scrivere il nostro contratto sulla catena di Goeli, e eth_getTransactionByHash(opens in a new tab), che è una richiesta di leggere le informazioni sulla nostra transazione in base all'hash. Per saperne di più sull'invio di transazioni, dai un'occhiata al nostro tutorial sull'invio di transazioni usando Web3.

Parte 2: interagisci con il tuo contratto intelligente

Adesso che abbiamo distribuito con successo un contratto intelligente nella rete di Goerli, impariamo come interagire con esso.

Crea un file interact.js

Questo è il file dove scriveremo il nostro script di interazione. Useremo la libreria Ethers.js che hai installato precedentemente nella Parte 1.

All'interno della cartella scripts/, crea un nuovo file chiamato interact.js e aggiungi il seguente codice:

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS

Aggiorna il tuo file .env

Useremo delle nuove variabili di ambiente, quindi dobbiamo definirle nel file .env che abbiamo creato in precedenza.

Dovremo aggiungere una definizione per la nostra CHIAVE_API di Alchemy e il CONTRACT_ADDRESS dove è stato distribuito il tuo contratto intelligente.

Il tuo file .env dovrebbe assomigliare a questo:

# .env
API_URL = "https://eth-goerli.alchemyapi.io/v2/<your-api-key>"
API_KEY = "<your-api-key>"
PRIVATE_KEY = "<your-metamask-private-key>"
CONTRACT_ADDRESS = "0x<your contract address>"

Prendi l'ABI del tuo contratto

L' del nostro contratto è l'interfaccia per interagire con il nostro contratto intelligente. Hardhat genera automaticamente l'ABI e la salva in HelloWorld.json. Per usare l'ABI, dobbiamo analizzarne il contenuto aggiungendo le seguenti righe di codice al nostro file interact.js:

1// interact.js
2const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")

Se vuoi vedere l'ABI, puoi stamparla nella tua console:

1console.log(JSON.stringify(contract.abi))

Per vedere l'ABI stampata alla console, vai al terminale ed esegui:

npx hardhat run scripts/interact.js

Crea un'istanza del tuo contratto

Per interagire con il tuo contratto, dobbiamo creare un'istanza del contratto nel nostro codice. Per farlo con Ethers.js, dovremo lavorare con tre concetti:

  1. Fornitore - un fornitore di nodi che consente l'accesso in lettura e scrittura alla blockchain
  2. Firmatario - rappresenta un conto di Ethereum che può firmare transazioni
  3. Contratto - un oggetto Ethers.js che rappresenta uno specifico contratto distribuito sulla catena

Utilizzeremo l'ABI del contratto della fase precedente per creare la nostra istanza del contratto:

1// interact.js
2
3// Provider
4const alchemyProvider = new ethers.providers.AlchemyProvider(
5 (network = "goerli"),
6 API_KEY
7)
8
9// Signer
10const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
11
12// Contract
13const helloWorldContract = new ethers.Contract(
14 CONTRACT_ADDRESS,
15 contract.abi,
16 signer
17)
Mostra tutto

Per saperne di più su fornitori, firmatari e contratti consultare la documentazione di ethers.js(opens in a new tab).

Leggi il messaggio init

Ricordi quando abbiamo distribuito il nostro contratto con il initMessage = "Hello world!"? Ora andremo a leggere il messaggio memorizzato nel nostro contratto intelligente e a stamparlo nella console.

In JavaScript, quando si interagisce con le reti vengono usate funzioni asincrone. Per saperne di più sulle funzioni asincrone, leggi questo articolo di Medium(opens in a new tab).

Usa il codice qui sotto per chiamare la funzione message nel nostro contratto intelligente e leggere il messaggio init:

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("The message is: " + message)
8}
9main()
Mostra tutto

Dopo aver eseguito il file usando il comando npx hardhat run scripts/interact.js da terminale, dovremmo vedere questa risposta:

1The message is: Hello world!

Congratulazioni! Hai appena letto con successo i dati dal contratto intelligente dalla blockchain Ethereum, complimenti!

Aggiorna il messaggio

Invece di limitarci a leggere il messaggio, possiamo anche aggiornare il messaggio salvato nel nostro contratto intelligente usando la funzione update! Piuttosto forte, vero?

Per aggiornare il messaggio, possiamo chiamare direttamente la funzione update nel nostro oggetto Contratto instanziato:

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("The message is: " + message)
8
9 console.log("Updating the message...")
10 const tx = await helloWorldContract.update("This is the new message.")
11 await tx.wait()
12}
13main()
Mostra tutto

Da notare che alla riga 11, facciamo una chiamata a .wait() all'oggetto transazione restituito. Questo assicura che il nostro script aspetti che la transazione venga minata sulla blockchain prima di uscire dalla funzione. Se la chiamata .wait() non viene inclusa, lo script potrebbe non vedere il valore message aggiornato nel contratto.

Leggi il nuovo messaggio

Ora dovresti essere capace di ripetere la fase precedente per leggere il valore message aggiornato. Prenditi un momento e vedi se riesci ad apportare le modifiche necessarie per stampare il nuovo valore!

Se hai bisogno di un suggerimento, ecco come dovrebbe apparire il tuo file interact.js a questo punto:

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS
6
7const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
8
9// provider - Alchemy
10const alchemyProvider = new ethers.providers.AlchemyProvider(
11 (network = "goerli"),
12 API_KEY
13)
14
15// signer - you
16const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
17
18// contract instance
19const helloWorldContract = new ethers.Contract(
20 CONTRACT_ADDRESS,
21 contract.abi,
22 signer
23)
24
25async function main() {
26 const message = await helloWorldContract.message()
27 console.log("The message is: " + message)
28
29 console.log("Updating the message...")
30 const tx = await helloWorldContract.update("this is the new message")
31 await tx.wait()
32
33 const newMessage = await helloWorldContract.message()
34 console.log("The new message is: " + newMessage)
35}
36
37main()
Mostra tutto

Adesso esegui solo script e dovresti essere in grado di vedere il vecchio messaggio, lo stato di aggiornamento e il nuovo messaggio stampato sul tuo terminale!

npx hardhat run scripts/interact.js --network goerli

1The message is: Hello World!
2Updating the message...
3The new message is: This is the new message.

Durante l'esecuzione dello script, potresti notare che la fase Updating the message... richiede del tempo di caricamento prima che venga caricato il nuovo messaggio. Questo è dovuto al processo di mining; se sei curioso di tracciare le transazioni mentre vengono minate, visita la mempool di Alchemy(opens in a new tab) per vedere lo stato di una transazione. Se la transazione viene eliminata, è sempre utile dare un'occhiata all'Etherscan di Goerli(opens in a new tab) e cercare l'hash della tua transazione.

Parte 3: pubblica il tuo contratto intelligente su Etherscan

Hai fatto tutto il lavoro duro per dare vita il tuo contratto intelligente, ora è arrivato il momento di condividerlo con il mondo!

Verificando il tuo contratto intelligente su Etherscan, chiunque può vedere il codice sorgente e interagire con il tuo contratto intelligente. Iniziamo!

Fase 1: genera una chiave API nel tuo conto di Etherscan

La chiave API di Etherscan è necessaria per verificare che tu possieda il contratto intelligente che stai cercando di pubblicare.

Se non hai ancora un conto di Etherscan registrati per avere un conto(opens in a new tab).

Una volta effettuato l'accesso, cerca il tuo nome utente nella barra di navigazione, passaci sopra e seleziona il pulsante Il mio profilo.

Nella tua pagina del profilo, dovresti vedere una barra di navigazione laterale. Dalla barra di navigazione laterale, seleziona Chiavi API. Quindi, premi il pulsante "Aggiungi" per creare una nuova chiave API, dai un nome alla tua app hello-world e premi il pulsante Crea una nuova chiave API.

La tua nuova chiave API dovrebbe apparire nella tabella delle chiavi API. Copia la chiave API nei tuoi appunti.

Successivamente, dobbiamo aggiungere la chiave API di Etherscan al nostro file .env.

Dopo averla aggiunta, il file .env dovrebbe apparire così:

1API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
2PUBLIC_KEY = "your-public-account-address"
3PRIVATE_KEY = "your-private-account-address"
4CONTRACT_ADDRESS = "your-contract-address"
5ETHERSCAN_API_KEY = "your-etherscan-key"

Contratti intelligenti distribuiti su Hardhat

Installa hardhat-etherscan

Pubblicare i tuoi contratti su Ethereum usando Hardhat è semplice. Per iniziare è necessario installare il plugin hardhat-etherscan. hardhat-etherscan verificherà automaticamente il codice sorgente e la ABI del contratto intelligente su Etherscan. Per aggiungerlo, esegui dalla cartella hello-world:

1npm install --save-dev @nomiclabs/hardhat-etherscan

Una volta installato, includi la seguente dichiarazione in cima al tuo hardhat.config.js e aggiungi le opzioni di configurazione di Etherscan:

1// hardhat.config.js
2
3require("dotenv").config()
4require("@nomiclabs/hardhat-ethers")
5require("@nomiclabs/hardhat-etherscan")
6
7const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env
8
9module.exports = {
10 solidity: "0.7.3",
11 defaultNetwork: "goerli",
12 networks: {
13 hardhat: {},
14 goerli: {
15 url: API_URL,
16 accounts: [`0x${PRIVATE_KEY}`],
17 },
18 },
19 etherscan: {
20 // Your API key for Etherscan
21 // Obtain one at https://etherscan.io/
22 apiKey: ETHERSCAN_API_KEY,
23 },
24}
Mostra tutto

Verifica il tuo contratto intelligente su Etherscan

Assicurati che tutti i file siano stati salvati e che tutte le variabili .env siano correttamente configurate.

Esegui l'attività verify, passando l'indirizzo del contratto e la rete dove è stato distribuito:

1npx hardhat verify --network goerli DEPLOYED_CONTRACT_ADDRESS 'Hello World!'

Assicurati che DEPLOYED_CONTRACT_ADDRESS sia l'indirizzo del tuo contratto intelligente distribuito sulla rete di prova di Goerli. Inoltre, l'argomento finale ('Hello World!') deve essere lo stesso valore di stringa usato durante la fase di distribuzione nella parte 1.

Se tutto va bene, nel tuo terminale vedrai il seguente messaggio:

1Successfully submitted source code for contract
2contracts/HelloWorld.sol:HelloWorld at 0xdeployed-contract-address
3for verification on Etherscan. Waiting for verification result...
4
5
6Successfully verified contract HelloWorld on Etherscan.
7https://goerli.etherscan.io/address/<contract-address>#contracts

Congratulazioni! Il codice del tuo contratto intelligente è su Etherscan!

Dai un'occhiata al tuo contratto intelligente su Etherescan!

Quando vai al link fornito nel tuo terminale, dovresti essere in grado di vedere il codice e l'ABI del tuo contratto intelligente pubblicati su Etherscan!

Fantastico: ce l'hai fatta! Ora chiunque può chiamare o scrivere sul tuo contratto intelligente! Non vediamo l'ora di vedere cosa svilupperai in futuro!

Parte 4: integra il tuo contratto intelligente con il frontend

Alla fine di questo tutorial, saprai:

  • Collegare un portafoglio di MetaMask alla tua dapp
  • Leggere dati da un contratto intelligente usando le API Web3 di Alchemy(opens in a new tab)
  • Firmare le transazioni di Ethereum usando MetaMask

Per questa dapp, useremo React(opens in a new tab) come nostro framwork di frontend; tuttavia è importante notare che non dedicheremo molto tempo alla descrizione dei suoi fondamentali, poiché ci concentreremo principalmente sull'introduzione delle funzionalità web3 nel nostro progetto.

Come prerequisito, è necessario avere una comprensione di React a livello principiante. In caso contrario, suggeriamo di completare il tutorial Introduzione a React(opens in a new tab) ufficiale.

Clonare i file iniziali

Per prima cosa, vai al repository hello-world-part-four in Github(opens in a new tab) per ottenere i file iniziali per questo progetto e clona questo repository nella tua macchina in locale.

Apri il repository clonato localmente. Si noti che contiene due cartelle starter-files e completed.

  • starter-files- lavoreremo in questa cartella, collegheremo l'interfaccia utente al tuo portafoglio di Ethereum e al contratto intelligente che abbiamo pubblicato nella Parte 3.
  • completed contiene l'intero tutorial completato e deve essere usata solo come riferimento se ci si blocca.

Quindi, apri la tua copia di starter-files con il tuo editor di codice preferito, e poi vai alla cartella src.

Tutto il codice che scriveremo sarà sotto la cartella src. Modificheremo il componente HelloWorld.js e i file Javascript util/interact.js per dare al nostro progetto la funzionalità Web3.

Dai un'occhiata ai file iniziali

Prima di iniziare a programmare, diamo un'occhiata a ciò che ci viene fornito nei file iniziali.

Metti in funzione il tuo progetto di React

Iniziamo eseguendo il progetto di React nel browser. La bellezza di React è che una volta eseguito il nostro progetto nel browser, ogni modifica che salviamo sarà aggiornata dal vivo nel browser.

Per mettere il progetto in funzione, vai alla cartella di root della cartella starter-files ed esegui npm install nel terminale per installare le dipendenze del progetto:

cd starter-files
npm install

Una volta terminata l'installazione, esegui npm start nel terminale:

npm start

Così facendo, dovrebbe aprirsi http://localhost:3000/(opens in a new tab) nel browser, dove vedrai il frontend per il nostro progetto. Dovrebbe consistere in un campo (un posto per aggiornare il messaggio memorizzato nel tuo contratto intelligente), il pulsante "Connetti portafoglio" e un pulsante "Aggiorna".

Se provi a fare clic su uno dei pulsanti, noterai che non funzionano - questo perché devi ancora programmarne la funzionalità.

Il componente HelloWorld.js

Torniamo la cartella src nell'editor e apriamo il file HelloWorld.js. È davvero importante comprendere tutto il contenuto di questo file, che è il componente principale di React su cui lavoreremo.

All'inizio di questo file, noterai che abbiamo diverse dichiarazioni relative all'importazione che sono necessarie per far funzionare il nostro progetto, inclusa la libreria di React, gli hook useEffect ed useState, alcuni elementi da ./util/interact.js (li descriveremo in dettaglio a breve!) e il logo di Alchemy.

1// HelloWorld.js
2
3import React from "react"
4import { useEffect, useState } from "react"
5import {
6 helloWorldContract,
7 connectWallet,
8 updateMessage,
9 loadCurrentMessage,
10 getCurrentWalletConnected,
11} from "./util/interact.js"
12
13import alchemylogo from "./alchemylogo.svg"
Mostra tutto

Successivamente, abbiamo le nostre variabili di stato che aggiorneremo dopo eventi specifici.

1// HelloWorld.js
2
3//State variables
4const [walletAddress, setWallet] = useState("")
5const [status, setStatus] = useState("")
6const [message, setMessage] = useState("No connection to the network.")
7const [newMessage, setNewMessage] = useState("")

Ecco cosa rappresenta ciascuna variabile:

  • walletAddress - una stringa che memorizza l'indirizzo del portafoglio dell'utente
  • status- una stringa che memorizza un messaggio utile che guida l'utente su come interagire con la dapp
  • message - una stringa che memorizza il messaggio corrente nel contratto intelligente
  • newMessage - una stringa che memorizza il nuovo messaggio che verrà scritto nel contratto intelligente

Dopo le variabili di stato, vedrai cinque funzioni non implementate: useEffect ,addSmartContractListener, addWalletListener , connectWalletPressed e onUpdatePressed. Illustreremo cosa fanno qui di seguito:

1// HelloWorld.js
2
3//called only once
4useEffect(async () => {
5 //TODO: implement
6}, [])
7
8function addSmartContractListener() {
9 //TODO: implement
10}
11
12function addWalletListener() {
13 //TODO: implement
14}
15
16const connectWalletPressed = async () => {
17 //TODO: implement
18}
19
20const onUpdatePressed = async () => {
21 //TODO: implement
22}
Mostra tutto
  • useEffect(opens in a new tab) - questo è un hook di React chiamato dopo il rendering del tuo componente. Poiché in essa viene passato un array vuoto [] (vedi la riga 4), sarà chiamata solo al primo rendering del componente. Qui caricheremo il messaggio corrente memorizzato nel nostro contratto intelligente, chiameremo i listener del nostro contratto intelligente e del portafoglio e aggiorneremo la nostra interfaccia utente per riflettere se un portafoglio sia già connesso.
  • addSmartContractListener- questa funzione configura un listener che osserverà l'evento UpdatedMessages del nostro contratto HelloWorld e aggiornerà la nostra interfaccia utente quando il messaggio è cambiato nel nostro contratto intelligente.
  • addWalletListener- questa funzione configura un listener che rileva i cambiamenti nel portafoglio di MetaMask dell'utente, come quando un utente disconnette il suo portafoglio o cambia indirizzo.
  • connectWalletPressed- questa funzione sarà chiamata per connettere il portafoglio di MetaMask dell'utente alla nostra dapp.
  • onUpdatePressed - questa funzione sarà chiamata quando un utente vuole aggiornare il messaggio memorizzato nel contratto intelligente.

Vicino alla fine di questo file, abbiamo l'UI del nostro componente.

1// HelloWorld.js
2
3//the UI of our component
4return (
5 <div id="container">
6 <img id="logo" src={alchemylogo}></img>
7 <button id="walletButton" onClick={connectWalletPressed}>
8 {walletAddress.length > 0 ? (
9 "Connected: " +
10 String(walletAddress).substring(0, 6) +
11 "..." +
12 String(walletAddress).substring(38)
13 ) : (
14 <span>Connect Wallet</span>
15 )}
16 </button>
17
18 <h2 style={{ paddingTop: "50px" }}>Current Message:</h2>
19 <p>{message}</p>
20
21 <h2 style={{ paddingTop: "18px" }}>New Message:</h2>
22
23 <div>
24 <input
25 type="text"
26 placeholder="Update the message in your smart contract."
27 onChange={(e) => setNewMessage(e.target.value)}
28 value={newMessage}
29 />
30 <p id="status">{status}</p>
31
32 <button id="publishButton" onClick={onUpdatePressed}>
33 Update
34 </button>
35 </div>
36 </div>
37)
Mostra tutto

Se analizzi questo codice con attenzione, noterai dove usiamo le varie variabili di stato nella nostra interfaccia utente:

  • Nelle righe 6-12, se il portafoglio dell'utente è connesso (ossia walletAddress.length > 0), viene visualizzata una versione troncata del walletAddress dell'utente nel pulsante con ID "walletButton"; altrimenti si leggerà semplicemente "Connect Wallet".
  • Nella riga 17, viene visualizzato il messaggio corrente memorizzato nel contratto intelligente, che è acquisito nella stringa message.
  • Nelle righe 23-26, usiamo un componente controllato(opens in a new tab) per aggiornare la nostra variabile di stato newMessage quando l'input nel campo di testo cambia.

Oltre alle variabili di stato, vedrai anche che le funzioni connectWalletPressed e onUpdatePressed vengono chiamate rispettivamente quando si fa clic sui pulsanti con ID publishButton e walletButton.

Infine vediamo dove viene aggiunto questo componente HelloWorld.js.

Se vai al file App.js, che è il componente principale su React e che agisce come contenitore per tutti gli altri componenti, vedrai che il nostro componente HelloWorld.js è inserito alla riga 7.

Per ultimo, ma non meno importante, diamo un'occhiata ad un altro file fornito, il file interact.js.

Il file interact.js

Poiché vogliamo rispettare il paradigma M-V-C(opens in a new tab), vorremo un file separato che contiene tutte le nostre funzioni per gestire la logica, i dati e le regole della nostra dapp, ed essere quindi in grado di esportare queste funzioni al nostro frontend (il nostro componenteHelloWorld.js).

👆🏽Questo è esattamente lo scopo del file interact.js!

Vai alla cartella utilnella tua directory src, e noterai che abbiamo incluso un file chiamato interact.js che conterrà tutte le funzioni e le variabili per l'interazione con il nostro contratto intelligente e con il portafoglio.

1// interact.js
2
3//export const helloWorldContract;
4
5export const loadCurrentMessage = async () => {}
6
7export const connectWallet = async () => {}
8
9const getCurrentWalletConnected = async () => {}
10
11export const updateMessage = async (message) => {}
Mostra tutto

Noterai che all'inizio del file abbiamo commentato l'oggetto helloWorldContract. Più avanti in questo tutorial, toglieremo il commento a questo oggetto e istanzieremo il nostro contratto intelligente in questa variabile, che esporteremo poi nel nostro componente HelloWorld.js.

Le quatto funzioni non implementate dopo il nostro oggetto helloWorldContract fanno quanto segue:

  • loadCurrentMessage - questa funzione gestisce la logica di caricamento del messaggio corrente salvato nel contratto intelligente. Effettuerà una chiamata read (leggi) al contratto intelligente Hello World usando l'API Web3 di Alchemy(opens in a new tab).
  • connectWallet - questa funzione connette il MetaMask dell'utente alla nostra dapp.
  • getCurrentWalletConnected - questa funzione verificherà se un conto di Ethereum è già connesso alla nostra dapp durante il caricamento della pagina e aggiornerà l'interfaccia utente di conseguenza.
  • updateMessage - questa funzione aggiornerà il messaggio memorizzato nel contratto intelligente. Effettuerà una chiamata di scrittura al contratto intelligente Hello World, quindi il portafoglio di MetaMask dell'utente dovrà firmare una transazione di Ethereum per aggiornare il messaggio.

Ora che ci è chiaro con cosa stiamo lavorando, cerchiamo di capire come leggere il nostro contratto intelligente!

Fase 3: leggi il tuo contratto intelligente

Per leggere il tuo contratto intelligente, dovrai configurare con successo:

  • Una connessione API alla catena Ethereum
  • Un'istanza caricata del tuo contratto intelligente
  • Una funzione per chiamare la funzione del tuo contratto intelligente
  • Un listener per rilevare gli aggiornamenti quando i dati letti dal contratto intelligente cambiano

Questo può sembrare un gran numero di passaggi, ma non preoccuparti! Ti spiegheremo come affrontarli passo dopo passo! :)

Stabilire una connessione API alla catena Ethereum

Ricordi che nella seconda parte di questo tutorial abbiamo usato la nostra chiave Web3 di Alchemy per leggere il nostro contratto intelligente(opens in a new tab)? Ti servirà anche una chiave Web3 di Alchemy nella tua dapp per leggere dalla caena.

Se non lo hai già, installa Alchemy Web3(opens in a new tab) andando nella cartella di root dei tuoi starter-files ed eseguendo il seguente comando nel tuo terminale:

1npm install @alch/alchemy-web3

Alchemy Web3(opens in a new tab) è un wrapper intorno aWeb3.js(opens in a new tab) che fornisce metodi API migliorati e altri benefici fondamentale per semplificare la tua vita a uno sviluppatore web3. È progettato per richiedere una configurazione minima, così da poter iniziare a usarlo immediatamente nella tua app!

Quindi, installa il pacchetto dotenv(opens in a new tab) nella cartella del tuo progetto, in modo da avere un posto sicuro per memorizzare la nostra chiave API dopo averla ottenuta.

1npm install dotenv --save

Per la nostra dapp, useremo la nostra chiave API Websockets invece della nostra chiave API HTTP, perché ci permetterà di configurare un listener che rilevi quando il messaggio memorizzato nel contratto intelligente cambia.

Una volta ottenuta la chiave API, crea un file .env nella tua cartella di root e aggiungici l'url di Alchemy Websockets. Successivamente, il tuo file .env dovrebbe avere il seguente aspetto:

1REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<key>

Ora siamo pronti a configurare il nostro endpoint Alchemy Web3 nella nostra dapp! Torniamo al nostro interact.js, che è contenuto nella cartella util e aggiungiamo il seguente codice all'inizio del file:

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8//export const helloWorldContract;

Sopra, abbiamo prima importato la chiave Alchemy dal nostro file .env e poi passato la nostra alchemyKey a createAlchemyWeb3 per stabilire il nostro endpoint di Alchemy Web3.

Con questo endpoint pronto, è il momento di caricare il nostro contratto intelligente!

Caricare il proprio contratto intelligente Hello World

Per caricare il tuo contratto intelligente Hello World, sono necessari l'indirizzo e l'ABI del contratto, entrambi reperibili su Etherscan se hai completato la Parte 3 di questo tutorial.

Come ottenere l'ABI del tuo contratto da Etherscan

Se hai saltato la Parte 3 di questo tutorial, puoi usare il contratto HelloWorld con l'indirizzo 0x6f3f635A9762B47954229Ea479b4541eAF402A6A(opens in a new tab). La sua ABI si trova qui(opens in a new tab).

L'ABI di un contratto serve per specificare quale funzione invocherà un contratto, oltre che per garantire che la funzione restituirà i dati nel formato previsto. Una volta copiata la ABI del nostro contratto, salviamola come file JSON chiamato contract-abi.json nella cartella src.

Il file contract-abi.json deve essere memorizzato nella cartella src.

Con l'indirizzo del contratto, l'ABI e l'endpoint di Alchemy Web3, possiamo usare il metodo del contratto(opens in a new tab) per caricare un'istanza del nostro contratto intelligente. Importa la ABI del contratto nel file interact.js e aggiungi l'indirizzo del tuo contratto.

1// interact.js
2
3const contractABI = require("../contract-abi.json")
4const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"

Ora possiamo finalmente eliminare il commento dalla nostra variabile helloWorldContract e caricare il contratto intelligente usando il nostro endpoint di AlchemyWeb3:

1// interact.js
2export const helloWorldContract = new web3.eth.Contract(
3 contractABI,
4 contractAddress
5)

Ricapitolando, le prime 12 righe del file interact.js dovrebbero avere questo aspetto:

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8const contractABI = require("../contract-abi.json")
9const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
10
11export const helloWorldContract = new web3.eth.Contract(
12 contractABI,
13 contractAddress
14)
Mostra tutto

Ora che il nostro contratto è stato caricato, possiamo implementare la nostra funzione loadCurrentMessage!

Implementare loadCurrentMessage nel proprio file interact.js

Questa funzione è semplicissima. Faremo una semplice invocazione asincrona web3 per leggere dal nostro contratto. La nostra funzione restituirà il messaggio memorizzato nel contratto intelligente:

Aggiorna loadCurrentMessage nel tuo file interact.js come segue:

1// interact.js
2
3export const loadCurrentMessage = async () => {
4 const message = await helloWorldContract.methods.message().call()
5 return message
6}

Poiché vogliamo visualizzare questo contratto intelligente nella nostra interfaccia utente, aggiorniamo la funzione useEffect nel nostro componente HelloWorld.js come segue:

1// HelloWorld.js
2
3//called only once
4useEffect(async () => {
5 const message = await loadCurrentMessage()
6 setMessage(message)
7}, [])

Si noti che vogliamo che il nostro loadCurrentMessage sia chiamato solo una volta durante il primo rendering del componente. Presto implementeremo addSmartContractListener per aggiornare automaticamente l'interfaccia utente dopo che il messaggio del contratto intelligente cambia.

Prima di addentrarci nel nostro listener, diamo un'occhiata a ciò che abbiamo finora! Salva i file HelloWorld.js e interact.js, quindi vai su http://localhost:3000/(opens in a new tab)

Noterai che il messaggio corrente non dice più "Nessuna connessione alla rete". Riflette invece il messaggio memorizzato nel contratto intelligente. Pazzesco!

L'interfaccia utente dovrebbe ora riflettere il messaggio memorizzato nel contratto intelligente

Ora, parlando di quel listener...

Implementa addSmartContractListener

Se ripensi al file HelloWorld.sol che abbiamo scritto nella Parte 1 di questa serie di tutorial(opens in a new tab), ricorderai che c'è un evento del contratto intelligente chiamato UpdatedMessages che viene emesso dopo aver invocato la funzione update del nostro contratto intelligente (vedi righe 9 e 27):

1// HelloWorld.sol
2
3// Specifies the version of Solidity, using semantic versioning.
4// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
5pragma solidity ^0.7.3;
6
7// Defines a contract named `HelloWorld`.
8// Un contratto è una raccolta di funzioni e dati (il suo stato). Una volta distribuito, un contratto risiede in un indirizzo specifico della blockchain Ethereum. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contract HelloWorld {
10
11 //Emitted when update function is called
12 //Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.
13 event UpdatedMessages(string oldStr, string newStr);
14
15 // Declares a state variable `message` of type `string`.
16 // Le variabili di stato sono variabili con valori memorizzati in modo permanente nello spazio di archiviazione (storage) del contratto. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.
17 string public message;
18
19 // Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.
20 // I costruttori sono utilizzati per inizializzare i dati del contratto. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
21 constructor(string memory initMessage) {
22
23 // Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).
24 message = initMessage;
25 }
26
27 // A public function that accepts a string argument and updates the `message` storage variable.
28 function update(string memory newMessage) public {
29 string memory oldMsg = message;
30 message = newMessage;
31 emit UpdatedMessages(oldMsg, newMessage);
32 }
33}
Mostra tutto

Gli eventi del contratto intelligente sono un modo per il contratto di comunicare che è successo qualcosa (cioè che c'è stato un evento) sulla blockchain alla tua applicazione front-end, che può essere "in ascolto" per eventi specifici e agire quando si verificano.

La funzione addSmartContractListener sarà in ascolto specificamente per l'evento UpdatedMessages del nostro contratto intelligente Hello World e aggiornerà l'interfaccia utente per visualizzare il nuovo messaggio.

Modifica addSmartContractListener come segue:

1// HelloWorld.js
2
3function addSmartContractListener() {
4 helloWorldContract.events.UpdatedMessages({}, (error, data) => {
5 if (error) {
6 setStatus("😥 " + error.message)
7 } else {
8 setMessage(data.returnValues[1])
9 setNewMessage("")
10 setStatus("🎉 Your message has been updated!")
11 }
12 })
13}
Mostra tutto

Analizziamo cosa succede quando il listener rileva un evento:

  • Se si verifica un errore quando viene emesso l'evento, questo si rifletterà nell'interfaccia utente tramite la variabile di stato status.
  • Altrimenti, si utilizzerà l'oggetto data restituito. data.returnValues è un array indicizzato a zero in cui il primo elemento dell'array memorizza il messaggio precedente e il secondo quello aggiornato. Complessivamente, in caso di evento concluso con successo, imposteremo la stringa message con il messaggio aggiornato, cancelleremo la stringa newMessage e aggiorneremo la variabile di stato status per rispecchiare il fatto che un nuovo messaggio è stato pubblicato sul nostro contratto intelligente.

Infine, chiamiamo il nostro listener nella funzione useEffect, in modo che venga inizializzato al primo rendering del componente HelloWorld.js. Complessivamente, la funzione useEffect dovrebbe avere questo aspetto:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7}, [])

Ora che siamo in grado di leggere dal nostro contratto intelligente, sarebbe bello anche capire come scrivergli! Tuttavia, per scrivere alla nostra dapp, dobbiamo prima avere un portafoglio di Ethereum collegato ad essa.

Quindi, ora ci occuperemo di configurare il nostro portafoglio di Ethereum (MetaMask) e di collegarlo alla nostra dapp!

Fase 4: configura il tuo portafoglio di Ethereum

Per scrivere qualsiasi cosa nella catena di Ethereum, gli utenti devono firmare le transazioni utilizzando le chiavi private del proprio portafoglio virtuale. Per questo tutorial, utilizzeremo MetaMask(opens in a new tab), un portafoglio virtuale nel browser utilizzato per gestire l'indirizzo del conto di Ethereum, in quanto rende la firma delle transazioni molto semplice per l'utente finale.

Se vuoi capire di più su come funzionano le transazioni su Ethereum, dai un'occhiata a questa pagina della Ethereum Foundation.

Scarica MetaMask

Puoi scaricare e creare gratuitamente un conto di MetaMask qui(opens in a new tab). Quando crei un conto, o se ne possiedi già uno, assicurati di passare alla "rete di prova di Goerli" in alto a destra (così da non avere a che fare con denaro reale).

Aggiungere ether da un Faucet

Per firmare una transazione sulla blockchain di Ethereum, abbiamo bisogno di un po' di Eth finti. Per ottenere Eth puoi andare su FaucETH(opens in a new tab) e inserire l'indirizzo del tuo conto di Goerli, cliccare su "Richiedi fondi", quindi selezionare "Rete di prova di Ethereum di Goerli" nel menu a discesa e infine fare clic di nuovo su "Richiedi fondi". Poco dopo, dovresti vedere gli Eth nel tuo conto di MetaMask!

Verifica il tuo saldo

Per ricontrollare che ci sia il saldo, facciamo una richiesta eth_getBalance(opens in a new tab) usando lo strumento compositore di Alchemy(opens in a new tab). Questo restituirà l'importo di Eth nel tuo portafoglio. Dopo aver inserito l'indirizzo del tuo conto di MetaMask e aver cliccato “Invia richiesta”, dovresti vedere una risposta come questa:

1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

NOTA: Questo risultato è in wei non in eth. Wei è usato come taglio più piccolo dell'ether. La conversione da wei a eth è: 1 eth = 10¹⁸ wei. Quindi se convertiamo 0xde0b6b3a7640000 in decimali, otteniamo 1*10¹⁸, pari a 1 eth.

Meno male! I nostri soldi finti ci sono tutti! 🤑

Fase 5: connetti MetaMask alla tua interfaccia utente

Ora che il nostro portafoglio di MetaMask è configurato, connettiamo la nostra dapp!

La funzione connectWallet

Nel nostro file interact.js, implementiamo la funzione connectWallet, che poi potremo chiamare nel nostro componente HelloWorld.js.

Modifichiamo connectWallet come segue:

1// interact.js
2
3export const connectWallet = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_requestAccounts",
8 })
9 const obj = {
10 status: "👆🏽 Write a message in the text-field above.",
11 address: addressArray[0],
12 }
13 return obj
14 } catch (err) {
15 return {
16 address: "",
17 status: "😥 " + err.message,
18 }
19 }
20 } else {
21 return {
22 address: "",
23 status: (
24 <span>
25 <p>
26 {" "}
27 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
28 You must install MetaMask, a virtual Ethereum wallet, in your
29 browser.
30 </a>
31 </p>
32 </span>
33 ),
34 }
35 }
36}
Mostra tutto

Cosa fa esattamente questo enorme blocco di codice?

Innanzitutto, controlla se window.ethereum sia abilitato nel browser.

window.ethereum è un'API globale, iniettata da MetaMask e altri fornitori di portafogli, che consente ai siti web di richiedere i conti di Ethereum degli utenti. Se approvato, può leggere i dati dalle blockchain a cui l'utente è connesso e suggerire all'utente di firmare messaggi e transazioni. Dai un'occhiata alla documentazione di MetaMask(opens in a new tab) per ulteriori informazioni!

Se window.ethereum non è presente, significa che MetaMask non è installato. Verrà quindi restituito un oggetto JSON in cui l'address restituito è una stringa vuota e l'oggetto JSX di status indica che l'utente deve installare MetaMask.

Ora, se window.ethereum è presente, le cose cominciano a farsi interessanti.

Usando una struttura try/catch, proveremo a connetterci a MetaMask chiamando window.ethereum.request({ method: "eth_requestAccounts" });(opens in a new tab). Chiamare questa funzione aprirà MetaMask nel browser, dove sarà richiesto all'utente di connettere il proprio portafoglio alla tua dapp.

  • Se l'utente sceglie di connettersi, il method: "eth_requestAccounts" restituirà un array contenente gli indirizzi di tutti gli account dell'utente connessi alla dapp. Nel complesso, la nostra funzione connectWallet restituirà un oggetto JSON contenente il primo address in questo array (vedi la riga 9) e un messaggio di status che richiede all'utente di scrivere un messaggio nello smart contract.
  • Se l'utente rifiuta la connessione, allora l'oggetto JSON conterrà una stringa vuota per l'address restituito e un messaggio di status che indica che l'utente ha rifiutato la connessione.

Ora che abbiamo scritto questa funzione connectWallet, il passo successivo è chiamarla nel nostro componente HelloWorld.js.

Aggiungi la funzione connectWallet al componente dell'interfaccia utente HelloWorld.js

Vai alla funzione connectWalletPressed in HelloWorld.js e aggiornala come segue:

1/ HelloWorld.js
2
3const c= async () => {
4 const walletResponse = await connectWallet()
5 setStatus(walletResponse.status)
6 setWallet(walletResponse.address)
7}

Nota come gran parte della nostra funzionalità è esterna al nostro componente HelloWorld.js dal file interact.js? Questo perché stiamo seguendo il modello M-V-C!

In connectWalletPressed, creiamo semplicemente una chiamata d'attesa alla nostra funzione connectWallet importata e, usando la sua risposta, aggiorniamo le nostre variabili status e walletAddress tramite i loro hook di stato.

Ora salviamo entrambi i file (HelloWorld.js e interact.js) e testiamo la nostra interfaccia utente.

Apri il browser sulla pagina http://localhost:3000/(opens in a new tab) e premi il pulsante "Connetti portafoglio" in alto a destra della pagina.

Se hai MetaMask installato, ti dovrebbe essere richiesto di connettere il tuo portafoglio alla tua dapp. Accetta l'invito a connetterti.

Dovresti vedere che il pulsante del portafoglio ora mostra che il tuo indirizzo è connesso! Sììììì 🔥

Prova quindi a ricaricare la pagina... questo è strano. Il nostro pulsante del portafoglio ci sta richiedendo di connetterci a MetaMask, anche se è già connesso...

Tuttavia, non temere! Possiamo facilmente risolvere questo problema implementando getCurrentWalletConnected, che verificherà se un indirizzo è già connesso alla nostra dapp e aggiornerà l'interfaccia utente di conseguenza!

La funzione getCurrentWalletConnected

Aggiorna la funzione getCurrentWalletConnected nel file interact.js come segue:

1// interact.js
2
3export const getCurrentWalletConnected = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_accounts",
8 })
9 if (addressArray.length > 0) {
10 return {
11 address: addressArray[0],
12 status: "👆🏽 Write a message in the text-field above.",
13 }
14 } else {
15 return {
16 address: "",
17 status: "🦊 Connect to MetaMask using the top right button.",
18 }
19 }
20 } catch (err) {
21 return {
22 address: "",
23 status: "😥 " + err.message,
24 }
25 }
26 } else {
27 return {
28 address: "",
29 status: (
30 <span>
31 <p>
32 {" "}
33 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
34 You must install MetaMask, a virtual Ethereum wallet, in your
35 browser.
36 </a>
37 </p>
38 </span>
39 ),
40 }
41 }
42}
Mostra tutto

Questo codice è molto simile alla funzione connectWallet che abbiamo appena scritto nella fase precedente.

La differenza principale è che, invece di chiamare il metodo eth_requestAccounts, che apre MetaMask perché l'utente connetta il proprio portafoglio, qui chiamiamo il metodo eth_accounts che, semplicemente, restituisce un insieme contenente gli indirizzi di MetaMask correntemente connessi alla nostra dapp.

Per vedere questa funzione in azione, chiamiamola nella funzione useEffect del nostro componente HelloWorld.js:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11}, [])
Mostra tutto

Nota che stiamo usando la risposta alla nostra chiamata a getCurrentWalletConnected per aggiornare le nostre variabili di stato walletAddress e status.

Ora che abbiamo aggiunto questo codice, proviamo ad aggiornare la finestra del browser.

Bellooooo! Il pulsante dovrebbe dire che sei connesso e mostrare un'anteprima dell'indirizzo del tuo portafoglio connesso, anche dopo un refresh!

Implementa addWalletListener

Il passaggio finale della configurazione del portafoglio della nostra dapp è implementare l'ascoltatore del portafoglio, così che la nostra UI si aggiorni al cambiamento dello stato del nostro portafoglio, ad esempio, quando l'utente si disconnette o cambia conto.

Nel tuo file HelloWorld.js, modifica la tua funzione addWalletListener come segue:

1// HelloWorld.js
2
3function addWalletListener() {
4 if (window.ethereum) {
5 window.ethereum.on("accountsChanged", (accounts) => {
6 if (accounts.length > 0) {
7 setWallet(accounts[0])
8 setStatus("👆🏽 Write a message in the text-field above.")
9 } else {
10 setWallet("")
11 setStatus("🦊 Connect to MetaMask using the top right button.")
12 }
13 })
14 } else {
15 setStatus(
16 <p>
17 {" "}
18 🦊 <a target="_blank" href={`https://metamask.io/download.html`}>
19 You must install MetaMask, a virtual Ethereum wallet, in your browser.
20 </a>
21 </p>
22 )
23 }
24}
Mostra tutto

Scommetto che a questo punto non hai bisogno del nostro aiuto per capire cosa succede qui, ma per completezza di informazioni, vediamo di analizzare velocemente la situazione:

  • Per prima cosa, la nostra funzione verifica se window.ethereum è abilitata (cioè se MetaMask è installato).
    • Se non lo è, impostiamo semplicemente la nostra variabile di stato statusa una stringa JSX che richiede all'utente di installare MetaMask.
    • Se è abilitato, configuriamo l'ascoltatore window.ethereum.on("accountsChanged") alla riga 3, affinché ascolti i cambiamenti di stato nel portafoglio di MetaMask, tra cui, quando l'utente connette un ulteriore conto alla dapp, cambia conto, o ne disconnette uno. Se è connesso almeno un conto, la variabile di stato walletAddress è aggiornata come primo conto nell'insieme accounts, restituito dall'ascoltatore. Altrimenti, walletAddress è impostato come una stringa vuota.

Infine ma non meno importante, dobbiamo chiamare la funzione useEffect:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11
12 addWalletListener()
13}, [])
Mostra tutto

E questo è tutto! Abbiamo completato con successo la programmazione di tutte le funzionalità del nostro portafoglio! Passiamo ora all'ultimo compito: aggiornare il messaggio memorizzato nel contratto intelligente!

Fase 6: implementa la funzione updateMessage

Bene, siamo in dirittura d'arrivo! Nel updateMessage del tuo file interact.js, faremo quanto segue:

  1. Assicurarci che il messaggio che vogliamo pubblicare nel nostro contratto intelligente sia valido
  2. Firmare la transazione usando MetaMask
  3. Chiamare questa funzione dal componente del frontend HelloWorld.js

Non ci vorrà molto; finiamo questa dapp!

Gestione degli errori d'input

Naturalmente, è utile avere una certa gestione degli errori di input all'inizio della funzione.

Vorremo che la nostra funzione ci faccia sapere subito se non c'è un'estensione MetaMask installata, se non c'è un portafoglio connesso (ossia address passato è una stringa vuota) o se message è una stringa vuota. Aggiungiamo la seguente gestione degli errori a updateMessage:

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 if (!window.ethereum || address === null) {
5 return {
6 status:
7 "💡 Connect your MetaMask wallet to update the message on the blockchain.",
8 }
9 }
10
11 if (message.trim() === "") {
12 return {
13 status: "❌ Your message cannot be an empty string.",
14 }
15 }
16}
Mostra tutto

Ora che la gestione degli errori d'input è gestita correttamente, è tempo di firmare la transazione con MetaMask!

Firmare la transazione

Se ti senti già a tuo agio con le transazioni web3 di Ethereum tradizionali, il codice che scriveremo in seguito ti risulterà molto familiare. Sotto il tuo codice di gestione degli errori di input, aggiungi quanto segue a updateMessage:

1// interact.js
2
3//set up transaction parameters
4const transactionParameters = {
5 to: contractAddress, // Required except during contract publications.
6 from: address, // must match user's active address.
7 data: helloWorldContract.methods.update(message).encodeABI(),
8}
9
10//sign the transaction
11try {
12 const txHash = await window.ethereum.request({
13 method: "eth_sendTransaction",
14 params: [transactionParameters],
15 })
16 return {
17 status: (
18 <span>
19{" "}
20 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
21 View the status of your transaction on Etherscan!
22 </a>
23 <br />
24 ℹ️ Once the transaction is verified by the network, the message will be
25 updated automatically.
26 </span>
27 ),
28 }
29} catch (error) {
30 return {
31 status: "😥 " + error.message,
32 }
33}
Mostra tutto

Analizziamo cosa sta accadendo. Per prima cosa importiamo i parametri delle transazioni, dove:

  • to specifica l'indirizzo del destinatario (il nostro smart contract)
  • from specifica il firmatario della transazione, la variabile address che abbiamo passato nella nostra funzione
  • data contiene la chiamata al metodo update del nostro contratto intelligente Hello World, ricevendo la nostra variabile stringa message come input

Creiamo quindi una chiamata d'attesa, window.ethereum.request in cui chiediamo a MetaMask di firmare la transazione. Nota che, alle righe 11 e 12, stiamo specificando il nostro metodo eth, eth_sendTransaction, e passando i nostri transactionParameters.

A questo punto, MetaMask si aprirà nel browser e richiederà all'utente di firmare o rifiutare la transazione.

  • Se la transazione va a buon fine, la funzione restituirà un oggetto JSON in cui la stringa JSX status richiede all'utente di controllare Etherscan per ulteriori informazioni sulla sua transazione.
  • Se la transazione non va a buon fine, la funzione restituirà un oggetto JSON in cui la stringa status trasmette il messaggio d'errore.

Complessivamente, la nostra funzione updateMessage dovrebbe avere questo aspetto:

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 //input error handling
5 if (!window.ethereum || address === null) {
6 return {
7 status:
8 "💡 Connect your MetaMask wallet to update the message on the blockchain.",
9 }
10 }
11
12 if (message.trim() === "") {
13 return {
14 status: "❌ Your message cannot be an empty string.",
15 }
16 }
17
18 //set up transaction parameters
19 const transactionParameters = {
20 to: contractAddress, // Required except during contract publications.
21 from: address, // must match user's active address.
22 data: helloWorldContract.methods.update(message).encodeABI(),
23 }
24
25 //sign the transaction
26 try {
27 const txHash = await window.ethereum.request({
28 method: "eth_sendTransaction",
29 params: [transactionParameters],
30 })
31 return {
32 status: (
33 <span>
34{" "}
35 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
36 View the status of your transaction on Etherscan!
37 </a>
38 <br />
39 ℹ️ Once the transaction is verified by the network, the message will
40 be updated automatically.
41 </span>
42 ),
43 }
44 } catch (error) {
45 return {
46 status: "😥 " + error.message,
47 }
48 }
49}
Mostra tutto

Infine, ma non meno importante, dobbiamo connettere la nostra funzioneupdateMessage al nostro componente HelloWorld.js.

Connetti updateMessageal frontend HelloWorld.js

La nostra funzione onUpdatePressed dovrebbe effettuare una chiamata d'attesa alla funzione updateMessage importata e modificare la variabile di stato status per riflettere se la transazione è andata o meno a buon fine:

1// HelloWorld.js
2
3const onUpdatePressed = async () => {
4 const { status } = await updateMessage(walletAddress, newMessage)
5 setStatus(status)
6}

È super chiaro e semplice. E indovina un po'... LA TUA DAPP È COMPLETA!!!

Prosegui e pova il pulsante Aggiorna!

Crea la tua dapp personalizzata

Fantastico, sei arrivato alla fine del tutorial! Per ricapitolare, hai imparato a:

  • Collegare un portafoglio di MetaMask al tuo progetto dapp
  • Leggere dati da un contratto intelligente usando le API Web3 di Alchemy(opens in a new tab)
  • Firmare le transazioni di Ethereum usando MetaMask

Ora hai tutti gli strumenti applicare le competenze apprese in questo tutorial per costruire il tuo progetto dapp! Come sempre, se hai domande, non esitare a contattarci per chiedere aiuto sul Discord di Alchemy(opens in a new tab). 🧙‍♂️

Una volta completato questo tutorial, facci sapere come è stata la tua esperienza o se hai qualche feedback taggandoci su Twitter @alchemyplatform(opens in a new tab)!

Ultima modifica: @Carla78(opens in a new tab), 15 novembre 2023

Questo tutorial è stato utile?