Exemple d'application Web 2.0/Web 3.0 de chaîne de blocs Ethereum

11.6 Exemple d'application Web 2.0/Web 3.0 de chaîne de blocs Ethereum

Objectifs d'apprentissage

À la fin de cette section, vous serez capable de :

  • Construire une application hybride Web 3.0 de chaîne de blocs Ethereum
  • Créer l'application React et installer les dépendances
  • Créer le contrat intelligent
  • Créer les composants frontaux React
  • Ajouter Web3 à l'application React
  • Configurer l'application React pour communiquer avec le contrat intelligent

Jusqu'à présent, ce chapitre a exploré comment développer une application web Todo, ainsi qu'une application mobile Todo, en utilisant Bootstrap, Django, React (React), React Native (React Native) et Node (Node). Dans cette dernière section, vous apprendrez à créer une application Todo simple en utilisant React avec le Web 3.0 alimenté par des contrats intelligents (smart contract) Ethereum sur la chaîne de blocs.

Création d'une application Todo Web 3.0 de chaîne de blocs Ethereum

Comme vous l'avez appris, les applications Todo peuvent être créées à l'aide de différents outils. Dans cette section, vous explorerez la chaîne de blocs Ethereum (Ethereum blockchain), qui crée un réseau pair-à-pair sécurisé grâce à l'utilisation d'un contrat intelligent, qui est un accord numérique sécurisé permettant aux utilisateurs de transiger directement entre eux via la chaîne de blocs. Vous utiliserez React pour créer une application Todo Web 3.0 de chaîne de blocs Ethereum.

Prérequis

Pour construire l'application Todo Web 3.0 (Web 3.0) de chaîne de blocs Ethereum, vous devriez utiliser React v17.0.2, Bootstrap v4.5.0, Node v14.17.5, Web 3.js v1.2.2, Truffle v5.0.2 et Solidity v0.8.11. De plus, Ganache est utilisé comme chaîne de blocs personnelle pour le développement. Pour commencer, faites ce qui suit :

  • Téléchargez et installez Ganache. Lancez Ganache et choisissez l'option de démarrage rapide Ethereum comme illustré à la Figure 11.46.
    Capture d'écran de l'option Ethereum.
    Figure 11.46 Ceci est l'option de démarrage rapide Ethereum disponible après l'installation de Ganache. (source : Ganache, sous licence MIT)
  • L'étape suivante consiste à installer le plug-in Chrome MetaMask. Configurez le compte MetaMask et connectez-vous.

Création de l'application React

Précédemment dans ce chapitre, vous avez créé des applications React. Pour cette application, vous devez créer une nouvelle application React.

Pour créer la nouvelle application React, exécutez la commande suivante :

$ npx create-react-app ethreact

Installer Bootstrap, Web3 et d'autres dépendances

Pour utiliser Bootstrap et Web3 dans une application React, les paquets correspondants doivent être installés. Pour ce faire, dans le répertoire ethreact/, exécutez les commandes suivantes :

  $ npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps
  $ npm install react-bootstrap
  $ npm install web3@1.2.2
  

Ensuite, importez le fichier CSS de Bootstrap dans l'application React. Ouvrez ethreact/src/index.js et ajoutez l'importation Bootstrap suivante.

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Création du contrat intelligent

Pour développer le contrat intelligent Ethereum, vous utiliserez le cadriciel Truffle (Truffle Framework), qui est une suite d'outils populaires utilisée pour développer des contrats intelligents. Implémentez le cadriciel Truffle en utilisant Solidity (Solidity), qui est un langage de haut niveau, orienté objet, axé sur l'implémentation de contrats intelligents. Pour utiliser le cadriciel Truffle, dans le répertoire ethreact/, exécutez la commande d'installation suivante :

$ npm install -g [email protected]

Pour configurer l'application React afin d'utiliser Truffle, dans le répertoire ethreact/, exécutez la commande suivante, ce qui peut prendre plusieurs minutes.

$ truffle init

Lorsque l'initialisation est terminée, le répertoire contracts/ et le fichier Migrations.sol seront générés. De plus, le fichier truffle-config.js sera généré, comme mis en évidence à la Figure 11.47.

Capture d'écran du fichier truffle-config.js.
Figure 11.47 Voici le fichier truffle-config.js. (rendu avec Truffle par Truffle Security Co., sous licence MIT ; source : Copyright Rice University, OpenStax, sous licence CC BY 4.0)

Pour créer le contrat intelligent (smart contract), dans le répertoire ethreact/contracts/, créez le fichier TodoList.sol et ajoutez le code suivant.

pragma solidity ^0.5.0;
contract TodoList {
  uint public taskCount = 0;

  struct Task {
    uint id;
    string content;
    bool completed;
  }

  mapping(uint => Task) public tasks;

  event TaskCreated(
    uint id,
    string content,
    bool completed
  );

  event TaskCompleted(
    uint id,
    bool completed
  );

  constructor () public {
    createTask("Check out dappuniversity.com");
  }

  function createTask(string memory _content) public {
    taskCount ++;
    tasks[taskCount] = Task(taskCount, _content, false);
    emit TaskCreated(taskCount, _content, false);
  }
  function toggleCompleted(uint _id) public {
    Task memory _task = tasks[_id];
    _task.completed = !_task.completed;
    tasks[_id] = _task;
    emit TaskCompleted(_id, _task.completed);
  }

}

Ensuite, compilez le contrat intelligent. Dans le répertoire ethreact/, exécutez la commande suivante :

  $ truffle compile
  

Cette commande devrait fournir un statut confirmant que le contrat intelligent a été compilé avec succès, comme le montre le code suivant :

> Compiled successfully using:
  - solc: 0.5.16+commit.9c3226ce.Emscription.clang

Figure 11.48 montre comment ce processus générera quelques fichiers, y compris deux fichiers JSON dans le répertoire build/contracts/ et un fichier de migration dans le répertoire migrations/.

Capture d'écran des fichiers JSON et de migration.
Figure 11.48 Ce sont les fichiers JSON et de migration générés lors de la compilation du contrat intelligent. (rendu en utilisant JSON ; source : Copyright Rice University, OpenStax, sous licence CC BY 4.0)

Le fichier TodoList.json dans le répertoire build/contracts/ est le fichier ABI (Abstract Binary Interface) du contrat intelligent. Ce fichier contient ce qui suit :

  • bytecode compilé à partir du code du contrat intelligent Solidity qui peut s'exécuter sur la machine virtuelle Ethereum (EVM)
  • une représentation JSON du contrat intelligent.

Ensuite, configurez l'application React pour qu'elle se connecte au réseau de chaîne de blocs Ganache. Pour ce faire, ouvrez le fichier ethreact/truffle-config.js et décommentez les deux sections affichées. Sous « networks », activez l'hôte et le port de connexion au réseau de chaîne de blocs Ganache. Assurez-vous que l'hôte et le port sont synchronisés avec les paramètres suivants dans Ganache.

development: {
  host: "127.0.0.1",     // Localhost (default: none)
  port: 7545,            // Standard Ethereum port (default: none)
  network_id: "*",       // Any network (default: none)
},

Ensuite, sous « compilers », activez l'optimiseur solc.

// Configure your compilers
compilers: {
  solc: {
    // version: "0.5.1",    // Fetch exact version from solc-bin (default: truffle's version)
    // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
    // settings: {          // See the solidity docs for advice about optimization and evmVersion
     optimizer: {
       enabled: true,
       runs: 200
     },
    //  evmVersion: "byzantium"
    // }
  }
},

Ensuite, créez un script de migration pour déployer le contrat intelligent sur le réseau de chaîne de blocs Ganache. Dans le répertoire ethreact/migrations/, créez le fichier 2_deploy_contracts.js et ajoutez le code suivant.

var TodoList = artifacts.require("./TodoList.sol");

module.exports = function(deployer) {
  deployer.deploy(TodoList);
};

L'étape suivante consiste à migrer le contrat. Dans le répertoire ethreact/, exécutez cette commande.

$ truffle migrate

Figure 11.49 montre comment le contrat sera migré et fournit les détails de la transaction. Vous devriez noter l'adresse du contrat dans la sortie car elle sera ajoutée au fichier config.js plus tard.

Capture d'écran des détails de la transaction pour la migration du contrat.
Figure 11.49 Voici les détails de la transaction pour la migration du contrat. (source : Copyright Rice University, OpenStax, sous licence CC BY 4.0)

Création des composants frontaux React

Le composant TodoList rend l'interface utilisateur pour l'application Todo. Dans le répertoire ethreact/src/, créez le répertoire components/. Dans le répertoire components/, créez le fichier TodoList.js, puis créez le composant TodoList.

import React, { Component } from 'react'

class TodoList extends Component {

  render() {
    return (
      <div id="content">
        <form onSubmit={(event) => {
          event.preventDefault()
          this.props.createTask(this.task.value)
        }}>
          <input
            id="newTask"
            ref={(input) => {
              this.task = input
            }}
            type="text"
            className="form-control"
            placeholder="Ajouter une tâche..."
            required />
          <input type="submit" hidden={true} />
        </form>
        <ul id="taskList" className="list-unstyled">
          { this.props.tasks.map((task, key) => {
            return(
              <div className="taskTemplate" className="checkbox" key={key}>
                <label>
                  <input
                  type="checkbox"
                  name={task.id}
                  defaultChecked={task.completed}
                  ref={(input) => {
                    this.checkbox = input
                  }}
                  onClick={(event) => {
                    this.props.toggleCompleted(this.checkbox.name) }}/>
                  <span className="content">{task.content}</span>
                </label>
              </div>
            )
          })}
        </ul>
        <ul id="completedTaskList" className="list-unstyled">
        </ul>
      </div>
    );
  }
}
export default TodoList;

Mettez à jour le fichier ethreact/src/App.js pour importer et rendre le composant TodoList.

render() {
  return (
    <div>
      <nav className="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
        <a className="navbar-brand col-sm-3 col-md-2 mr-0" href="#">ToDo</a>
        <ul className="navbar-nav px-3">
          <li className="nav-item text-nowrap d-none d-sm-none d-sm-block">
            <small><a className="nav-link" href="#"><span id="account"></span></a></small>
          </li>
        </ul>
      </nav>
      <div className="container-fluid">
        <div className="row">
          <main role="main" className="col-lg-12 d-flex justify-content-center">
            { this.state.loading
              ? <div id="loader" className="text-center"><p className="text-center">Chargement...</p></div>
              : <TodoList
                tasks={this.state.tasks}
                createTask={this.createTask}
                toggleCompleted={this.toggleCompleted} />
            }
          </main>
        </div>
      </div>
    </div>
  );
}

Ajout de Web3 à l'application React

L'étape suivante consiste à ajouter Web3 et à le configurer pour qu'il se connecte à Ganache. Pour ce faire, ouvrez ethreact/src/App.js, importez le paquet Web3 et ajoutez le code présenté ici. Dans ce code, une connexion web3 est instanciée en fournissant l'URL de Ganache (http://localhost:8545). Cela permet à l'application React de se connecter à la chaîne de blocs. Les informations du compte de la chaîne de blocs sont ensuite accédées et ajoutées à l'objet d'état de l'application React. Dans le répertoire ethreact/src/, créez le fichier getWeb3.js et ajoutez le code présenté. Ce code crée un objet Web3 via différentes approches de connexion selon l'approche utilisée (par exemple, type de navigateur DApp, utilisation de localhost).

import Web3 from "web3";

const getWeb3 = () =>
  new Promise((resolve, reject) => {
    // Wait for loading completion to avoid race conditions with web3 injection timing.
    window.addEventListener("load", async () => {
      // Navigateurs dapp modernes...
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum);
        try {
          // Request account access if needed
          await window.ethereum.enable();
          // Accounts now exposed
          resolve(web3);
        } catch (error) {
          reject(error);
        }
      }
      // Anciens navigateurs dapp...
      else if (window.web3) {
        // Utiliser le fournisseur de Mist/MetaMask.
        //const web3 = window.web3; // ORIG
        const web3 = window.web3.currentProvider.enable()
        console.log("Web3 injecté détecté.");
        resolve(web3);
      }
      // Fallback to localhost; use dev console port by default...
      else {
        const provider = new Web3.providers.HttpProvider(
          "http://127.0.0.1:8545"
        );
        const web3 = new Web3(provider);
        console.log("Aucune instance web3 injectée, utilisation de web3 local.");
        resolve(web3);
      }
    });
  });

export default getWeb3;

Configuration de l'application React (React) pour communiquer avec le contrat intelligent

Pour rendre les tâches du contrat intelligent disponibles pour l'application React, créez un fichier de configuration, ainsi que l'adresse du contrat et l'ABI. Pour ce faire, dans le répertoire ethreact/src/, créez le fichier config.js. Ensuite, créez deux variables : TODO_LIST_ADDRESS et TODO_LIST_ABI. Récupérez l'adresse du contrat qui a été fournie lors du déploiement du contrat intelligent sur la chaîne de blocs et assignez-la à TODO_LIST_ADDRESS. Ouvrez le fichier ethreact/build/contracts/TodoList.json et copiez uniquement la partie ABI et assignez-la à TODO_LIST_ABI.

export const TODO_LIST_ADDRESS = '0x9A93A9951c1cAcF0Cecc320Fa6aD4e05d55a2d02'

export const TODO_LIST_ABI = [
  {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "completed",
        "type": "bool"
      }
    ],
    "name": "TaskCompleted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "content",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "completed",
        "type": "bool"
      }
    ],
    "name": "TaskCreated",
    "type": "event"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "taskCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tasks",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "content",
        "type": "string"
      },
      {
        "internalType": "bool",
        "name": "completed",
        "type": "bool"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "string",
        "name": "_content",
        "type": "string"
      }
    ],
    "name": "createTask",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_id",
        "type": "uint256"
      }
    ],
    "name": "toggleCompleted",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Ensuite, mettez à jour ethreact/src/App.js. Importez getWeb3.js et ajoutez le code suivant à la méthode de cycle de vie componentDidMount(). Cela connectera au réseau de chaîne de blocs et chargera le contrat avant que les composants React ne soient rendus dans le navigateur.

componentDidMount = async () => {
  try {
    // Get network provider and web3 instance.
    const web3 = await getWeb3();

    // Utiliser web3 pour obtenir les comptes de l'utilisateur.
    const accounts = await web3.eth.getAccounts();

    // Obtenir l'instance du contrat.
    const networkId = await web3.eth.net.getId();
    let deployedNetwork = TodoListABI.networks[networkId];
    const todoListInstance = new web3.eth.Contract(
      TODO_LIST_ABI,
      deployedNetwork && deployedNetwork.address
    );

    // Set web3, accounts, and contract to the state, and then proceed with an
    // example of interacting with the contract's methods.
    this.setState({ web3, accounts, todoListContract: todoListInstance }, this.runExample);
  } catch (error) {
    // Catch any errors for any of the above operations.
    alert(
      'Échec du chargement de web3, des comptes ou du contrat. Vérifiez la console pour plus de détails.',
    );
    console.error(error);
  }
};

Lancez l'application React en exécutant la commande suivante :

$npm start

Lancez un navigateur avec le plug-in MetaMask installé et naviguez vers http://localhost:3000. Créez une tâche.