Une dApp entière : la ToDo List

Salut ! Dans ce tutoriel, on va apprendre comment créer un smart contract et une dApp ReactJS pour interagir avec le smart contract. Si t’es pas sûr de ce qu’est un smart contract ou une dApp, pas de panique, on va t’expliquer tout ça !

Un smart contract, c’est un programme informatique qui peut s’exécuter tout seul sur la blockchain. Ça permet de faire des contrats et des accords numériques sans avoir besoin d’un tiers de confiance. En gros, ça permet de faire des trucs automatiquement sans avoir besoin de passer par un humain. Les smart contracts sont écrits dans un langage de programmation appelé Solidity et ils sont exécutés sur une machine virtuelle qui s’appelle EVM.

Une dApp, c’est une application décentralisée. Ça veut dire que c’est une appli qui s’exécute pas sur un seul serveur, mais sur plein de serveurs à travers la blockchain. Les dApps sont souvent utilisées pour des trucs qui ont besoin de sécurité et de transparence, comme les échanges de crypto-monnaies par exemple.

Voici un exemple de contrat intelligent et de dApp pour gérer une todolist sur une blockchain EVM en utilisant Solidity et ReactJS.

Contrat intelligent Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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() {
        createTask(unicode"Exemple de tâche");
    }

    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);
    }
}

Ce contrat intelligent permet de créer une todolist en stockant les tâches sous forme de structures Task. Il y a deux fonctions principales : createTask et toggleCompleted. La première permet de créer une nouvelle tâche avec un contenu et un état initial (non terminé), tandis que la seconde permet de basculer l’état de la tâche (terminé ou non terminé).

On peut le taper tel quel dans Remix, l’IDE en ligne : https://remix.ethereum.org/ et le déployer sur Yuma le testnet #HorizenEON actuel.

Si c’est ta première fois sur Remix, voici quelques aides pour prendre tes marques avec l’interface :

Comment créer le nouveau contrat Todolist.sol dans Remix

Dans Remix, le code est compilé lorsqu’on sauve le fichier (Ctrl+S). Mais il y a également la touche verte (Play) en haut d’interface et un onglet dédié au compilateur à gauche.
On peut, dans Advanced configuration, activer les optimisations.

Compilation avec Remix

Pour déployer, il faut vérifier que Metamask est bien connecté et que le réseau Yuma y est sélectionné. Si c’est le cas, choisir « Injected Provider – Metamask » dans la liste déroulante Environment. Votre adresse actuelle apparaît alors dans le champ Account. Si ce n’est pas le cas, rechargez la page.

Ensuite, cliquez sur Deploy pour déployer le contrat actuel.

Déploiement via Remix
  • Sous la première flèche rouge, tu vérifies que tu est bien sur Yuma (id : 1662)
  • Tu déploies en appuyant sur le bouton Deploy sous la deuxième flèche rouge.
  • Tu copies ton adresse de déploiement sous la troisième flèche rouge.

Le code pour la dApp en ReactJS

Bien ! Passons à la partie Front. Avant de commencer, assure toi d’avoir Node.js installé sur ta machine.

Crée un nouveau dossier appelé todolist à la racine de ton projet. Dans ce dossier, initialise un nouveau projet ReactJS et installe la librairie ethers (en v5) en exécutant les commandes suivantes :

npx create-react-app .
npm install ethers@^5

Dans le fichier App.js, remplace le code existant par le code suivant :

import React, { useState } from 'react';
import { ethers } from 'ethers';
import TodolistABI from './contracts/TodolistABI.json';
import './App.css';

function App() {
  const [tasks, setTasks] = useState([]);
  const [taskContent, setTaskContent] = useState('');

  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();

  const todoListContract = new ethers.Contract(
    process.env.REACT_APP_TODO_LIST_ADDRESS,
    TodoListABI,
    signer
  );

  const getTasks = async () => {
    const count = await todoListContract.taskCount();
    const newTasks = [];

    for (let i = 1; i <= count; i++) {
      const task = await todoListContract.tasks(i);
      newTasks.push(task);
    }

    setTasks(newTasks);
  };

  const createTask = async (e) => {
    e.preventDefault();
    const { hash } = await todoListContract.createTask(taskContent);
    await provider.waitForTransaction(hash);
    await getTasks();
    setTaskContent('');
  };

  const toggleCompleted = async (taskId) => {
    const { hash } = await todoListContract.toggleCompleted(taskId);
    await provider.waitForTransaction(hash);
    await getTasks();
  };

  return (
    <div className="App">
      <h1>Todo List</h1>

      <form onSubmit={createTask}>
        <input
          type="text"
          placeholder="Ajouter une tâche"
          value={taskContent}
          onChange={(e) => setTaskContent(e.target.value)}
        />
        <button type="submit">Ajouter</button>
      </form>

      <div>
        {tasks.map((task) => (
          <div key={task.id}>
            <input
              type="checkbox"
              checked={task.completed}
              onChange={() => toggleCompleted(task.id)}
            />
            <span>{task.content}</span>
          </div>))
        }
      </div>
    </div>
  );
}

export default App;

Il faudra renseigner l’adresse du contract dans un fichier .env à la racine du projet (exemple) :

REACT_APP_TODO_LIST_ADDRESS="0x51cc958e61e7c3f3594f9E78EBFB85739b9714F8"

On devra ensuite créer un sous répertoire contracts dans le répertoire src.

Et enfin, copier l’ABI du smart contract généré lors de la compilation et la coller dans le fichier ./contracts/TodolistABI.json.

ABI à récupérer ici dans Remix

Pour tester, tape la commande suivante :

npm start

C’est un exemple simple, il faut donc penser à se connecter manuellement, via le wallet à la dApp et il n’y a aucune vérification que le réseau utilisé est le bon.