Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

🔌 Web3 Libraries : Le Pont vers l'EVM

Analyse détaillée de l'architecture DApp, des Providers (JSON-RPC), des Signers (EIP-1193) et des bibliothèques Web3.js vs Ethers.js.

1.1

Architecture DApp

Le "stack" Web3. Comparaison (Web2 vs Web3), rôle du frontend (React/Vue) et le "backend" public (Smart Contract).

DAppWeb2 vs Web3
2.1

Le "Provider" (Lecture)

Le point d'accès "lecture seule" (Read-Only). Le protocole JSON-RPC. Nœuds distants (Infura, Alchemy) vs Nœuds injectés (Metamask).

ProviderJSON-RPCInfura
3.1

Le "Signer" (Écriture)

L'accès "écriture" (Write-Access). Le Signer abstrait, la clé privée, et le standard EIP-1193 (le "Wallet").

SignerClé PrivéeEIP-1193
4.1

Librairie : Web3.js

La librairie "historique". Modules (`web3.eth`), syntaxe (`.call()` vs `.send()`), gestion des promesses et utilitaires (`.utils`).

Web3.js.call().send()
5.1

Librairie : Ethers.js

Le standard "moderne". Philosophie (Provider, Signer, Contract), syntaxe (`.connect()`), gestion de l'ENS, et légèreté.

Ethers.jsProviderSigner
6.1

Comparaison : Web3 vs Ethers

Analyse détaillée : Taille du bundle, facilité d'utilisation, documentation, gestion des erreurs, et popularité actuelle.

ComparatifPerformance
1.1 Architecture d'une DApp (Application Décentralisée)

Pour comprendre Ethers.js, il faut d'abord comprendre le "stack" technique d'une DApp et en quoi il diffère radicalement d'une application Web2 traditionnelle.

Architecture Web2 (Client-Serveur Centralisé)

L'architecture Web2 est basée sur la **confiance** en une entité centrale.

[ Utilisateur (Navigateur) ]
          |
          |  Requête HTTP (GET, POST)
          |  (ex: /api/v1/user/1)
          ▼
[ Backend (Serveur Privé) ]
  (ex: Node.js, Django, Java)
  (Hébergé sur AWS, GCP...)
          |
          |  Logique métier privée
          |  (Validation, calculs)
          ▼
[ Base de Données (Privée) ]
  (ex: PostgreSQL, MongoDB)
  • Frontend : Un simple "client" (React, Vue) qui affiche des données et envoie des requêtes.
  • Backend : Le **cerveau** de l'application. Il détient toute la logique métier (comment fonctionne un "like", un "paiement", etc.). Son code est **privé et propriétaire**.
  • Base de Données : Le **registre** de l'application (qui possède quoi). Elle est **privée et propriétaire**.
  • Le Problème : L'utilisateur doit faire 100% confiance à l'opérateur (ex: Facebook, sa banque) pour :
    1. Ne pas censurer ses actions.
    2. Ne pas modifier les règles (ex: les taux d'intérêt) sans préavis.
    3. Ne pas se faire hacker (la BDD est un point de défaillance unique).
    4. Ne pas "tomber en panne".
Architecture Web3 (Client-Nœud Décentralisé)

L'architecture Web3 remplace le "backend propriétaire" par un "backend public" (le Smart Contract) et la "base de données privée" par une "base de données publique" (la Blockchain). L'architecture est basée sur la **vérification** cryptographique.

[ Utilisateur (Navigateur) ]
  + [ Wallet (Metamask) ] <--- (Contient la Clé Privée / Signer)
          |
          |  Appel JS (Ethers.js)
          |  (ex: contract.balanceOf(user))
          ▼
[ Provider (Nœud RPC) ]
  (ex: Infura, Alchemy, ou le nœud du Wallet)
          |
          |  Requête JSON-RPC
          |  (ex: eth_call)
          ▼
[ Réseau P2P Ethereum (Nœud) ]
          |
          |  Exécution de l'EVM
          |
          ▼
[ Smart Contract (Public) ] <--- Le "Backend"
          |
          ▼
[ Blockchain (Public) ] <--- La "Base de Données"
  • Frontend (DApp) : C'est toujours une application React/Vue. Mais elle n'appelle pas une API HTTP. Elle utilise une bibliothèque (Ethers.js) pour formater des requêtes JSON-RPC.
  • Provider (Nœud RPC) : C'est le "point d'accès" au réseau. C'est un nœud Ethereum qui écoute les requêtes JSON-RPC. (Voir 2.1).
  • Backend (Smart Contract) : Le "backend" (ex: le protocole Uniswap) est un **smart contract public**. Sa logique est transparente, vérifiable par tous, et **immuable**.
  • Base de Données (Blockchain) : L'"état" (qui possède quoi) est le "World State" d'Ethereum, une base de données publique et sécurisée par le consensus.
  • Le "Pont" (Ethers.js / Web3.js) : C'est la **bibliothèque JavaScript** (comme Axios ou jQuery pour le Web2) qui abstrait la complexité de la communication. Elle permet au développeur React d'écrire `contract.myFunction()` au lieu de devoir formater manuellement une requête JSON-RPC complexe.
2.1 Le Rôle du "Provider" (Connexion Lecture Seule)

Le **Provider** (Fournisseur) est le concept le plus fondamental. C'est une abstraction pour une **connexion en lecture seule (read-only)** à la blockchain. Il "fournit" un accès aux données. Il ne peut pas signer de transactions car il ne détient pas de clés privées.

Analogie : C'est le "câble réseau" ou "l'endpoint d'API en lecture seule". Il vous permet de *regarder* le registre, mais pas d'y *écrire*.

Le Protocole : JSON-RPC

Les bibliothèques JS (Ethers/Web3) sont des "wrappers" (enveloppes) autour du protocole de communication standard d'Ethereum : **JSON-RPC** (Remote Procedure Call). C'est un protocole de requêtes POST (HTTP ou WebSocket) où le corps est un JSON.

Lorsque votre DApp (via Ethers.js) exécute : provider.getBlockNumber() ...

...Ethers.js envoie en réalité cette requête POST à l'endpoint du provider (ex: Infura) :

// REQUÊTE (envoyée par Ethers)
{
    "jsonrpc": "2.0",
    "method": "eth_blockNumber",  // La "fonction" RPC à appeler
    "params": [],
    "id": 1
}

// RÉPONSE (renvoyée par le Nœud Infura)
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": "0x12A9B11" // Le numéro de bloc (en hexadécimal)
}

Ethers.js reçoit "0x12A9B11", le convertit en nombre (19569169), et vous le renvoie. Ethers est un traducteur JavaScript <-> JSON-RPC.

Méthodes "Read-Only" (via Provider)
  • eth_getBlockNumber : Récupère le dernier numéro de bloc.
  • eth_getBalance(address) : Récupère le solde ETH d'un compte.
  • eth_call(transaction) : C'est la plus importante. Elle **exécute** une fonction d'un smart contract (ex: `balanceOf(user)`) sur le nœud, mais **sans créer de transaction** et sans la publier. C'est une simulation. Elle est utilisée pour *lire* l'état d'un contrat (fonctions `view` / `pure`). C'est **gratuit**.
Types de Providers (Où se connecter ?)
1. Provider de Nœud Centralisé (Node-as-a-Service)

Exemples : Infura, Alchemy, QuickNode.

Fonctionnement : Ces sociétés gèrent des flottes de nœuds Ethereum (Full Nodes) extrêmement optimisés. Elles vous vendent un "endpoint" (une URL) avec une clé API que vous mettez dans votre code.

const RPC_URL = "https://mainnet.infura.io/v3/VOTRE_CLE_API";
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  • Avantage : Extrêmement rapide, fiable (100% uptime), scalabilité massive.
  • Inconvénient : Centralisation. Si Infura tombe en panne (c'est arrivé), une grande partie du Web3 "tombe en panne". Risque de censure ou de collecte d'IP.
2. Provider Injecté (EIP-1193)

Exemple : `window.ethereum` (injecté par Metamask, Rabby, Brave...).

Fonctionnement : Le portefeuille de l'utilisateur (ex: Metamask) injecte son *propre* provider dans le navigateur (dans l'objet `window`).

if (window.ethereum) {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
} else {
    // L'utilisateur n'a pas de wallet !
}
  • Avantage : **Décentralisation.** Votre DApp n'impose pas un provider (Infura). Elle utilise le provider que *l'utilisateur* a configuré dans son Metamask (qui peut être Infura, Alchemy, ou son *propre* nœud local !). C'est aussi la **porte d'entrée vers le Signer** (voir 3.1).
  • Inconvénient : Dépend du bon vouloir de l'utilisateur (il doit avoir un wallet).
Exemple : Lire des Données (Ethers.js)

Voici un script "lecture seule" qui utilise un provider Infura pour lire des données publiques, sans avoir besoin d'un wallet.

// 1. Importer Ethers
import { ethers } from "ethers";

// 2. Définir le Provider (connexion à Infura via URL)
const RPC_URL = "https://mainnet.infura.io/v3/YOUR_API_KEY";
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);

// 3. Définir l'ABI (juste la fonction dont on a besoin) et l'adresse du contrat
const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const DAI_ABI = [
  "function name() view returns (string)",
  "function balanceOf(address) view returns (uint256)"
];

// 4. Créer l'instance du Contrat (lecture seule)
const daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, provider);

// 5. Exécuter les appels (lecture)
async function readData() {
  try {
    const blockNumber = await provider.getBlockNumber();
    console.log(`Block actuel: ${blockNumber}`);

    const vitalikBalance = await provider.getBalance("vitalik.eth"); // Ethers gère l'ENS !
    console.log(`Solde de Vitalik: ${ethers.utils.formatEther(vitalikBalance)} ETH`);

    const daiName = await daiContract.name();
    console.log(`Nom du token: ${daiName}`);
    
    const someBalance = await daiContract.balanceOf("0x...ADDRESS...");
    console.log(`Solde DAI: ${ethers.utils.formatUnits(someBalance, 18)}`); // Le DAI a 18 décimales
    
  } catch (error) {
    console.error("Erreur:", error);
  }
}

readData();
3.1 Le Rôle du "Signer" (Autorisation d'Écriture)

Si le **Provider** est la *lecture*, le **Signer** est l'**écriture**. Un Signer est un objet qui représente un compte Ethereum (EOA) et qui peut **signer cryptographiquement** des messages et des transactions. Il est (ou a accès à) une **Clé Privée**.

Analogie : Le Provider vous donne l'IBAN du compte (lecture). Le Signer détient le mot de passe (la clé privée) pour *autoriser* un virement (écriture).

Le Standard : EIP-1193 (Le Provider Injecté)

Pendant des années, la communication entre la DApp et le Wallet (Metamask) était chaotique. L'**EIP-1193** (Ethereum Improvement Proposal) a standardisé cette communication.

Il stipule que le wallet (Metamask) doit injecter un objet global `window.ethereum` qui est un **Provider** (au sens Ethers.js). La DApp *communique* avec cet objet.

Le Flux d'Autorisation (Connexion)
  1. DApp : "Hé, `window.ethereum`, j'ai besoin de connaître le compte de l'utilisateur."
  2. DApp (Code) : await provider.send("eth_requestAccounts", [])
  3. Metamask : Intercepte cet appel RPC. Ouvre une pop-up : "La DApp 'monsite.com' souhaite se connecter à votre compte."
  4. Utilisateur : Clique sur "Connecter".
  5. Metamask : Renvoie à la DApp l'adresse de l'utilisateur (ex: `['0x123...abc']`).

À ce stade, la DApp connaît l'identité de l'utilisateur. Elle peut maintenant utiliser ce provider pour *demander* des signatures.

Le Signer Abstrait (Ethers.js)

La DApp **NE VOIT JAMAIS LA CLÉ PRIVÉE**. La clé privée reste sécurisée dans le "vault" de Metamask (ou dans le Ledger).

Quand vous faites const signer = provider.getSigner() (avec le provider Metamask), Ethers.js crée un objet **Signer abstrait**. Cet objet n'a pas la clé privée, mais il sait *comment demander* à Metamask (via `window.ethereum`) de signer des choses.

[ DApp (Ethers.js) ]             [ Wallet (Metamask) ]
        |                                |
        |  1. "Signe-moi cette Tx"       |
        |  (Tx non signée)               |
        | -----------------------------> |
        |                                |  2. Pop-up: "Confirmer la Tx ?"
        |                                |     (Utilisateur clique "Confirmer")
        |                                |  3. Signature (avec Clé Privée)
        |                                |
        |  4. "Voici la Tx signée"       |
        |  (Tx signée)                   |
        | <----------------------------- |
        |                                |
        |  5. Envoi au Provider (Infura) |
        | -----------------------------> | [ Nœud RPC ]
Méthodes "Write-Access" (via Signer)

Ces méthodes RPC (gérées par le Signer) coûtent du **Gas** et modifient l'état de la blockchain.

  • eth_requestAccounts : (Vu ci-dessus). Demande à l'utilisateur de connecter son portefeuille.
  • eth_sendTransaction(transaction) : C'est la méthode de base pour *écrire*.
    • La DApp construit un objet transaction (to, value, data...).
    • Le Signer (Metamask) la reçoit, la fait signer par l'utilisateur, puis la publie sur le réseau.
    • Elle renvoie un **Hash de Transaction** (0x...) (le "reçu").
  • personal_sign(message) :
    • Demande à l'utilisateur de signer un message simple (ex: "Je certifie être le propriétaire de ce compte pour me connecter à monsite.com").
    • C'est utilisé pour l'**authentification** (Sign-In with Ethereum), pas pour des transactions.
    • C'est beaucoup moins cher (pas de Gas) car ça ne touche pas à la blockchain, c'est juste une preuve cryptographique.
Types de Signers (Hors-Wallet)

Bien que le "Wallet Provider" (EIP-1193) soit la norme pour les DApps, il existe d'autres types de signers (utilisés en backend) :

  • ethers.Wallet (Signer Logiciel) :
    const signer = new ethers.Wallet("0x...VOTRE_CLE_PRIVEE...", provider);

    Utilisé dans des scripts, des tests, ou des serveurs backend (bots). **Extrêmement dangereux** en frontend. La clé privée est en clair dans la mémoire du programme.

  • Hardware Wallets (Signer Matériel) : (ex: Ledger, Trezor). La clé privée est sur un appareil physique. Ethers.js peut communiquer avec eux (via WebUSB) pour demander des signatures.
Exemple : Écrire des Données (Ethers.js)

Ce script (côté frontend, ex: dans un composant React) montre le flux complet : connexion, récupération du signer, et envoi d'une transaction.

// (Supposons que Ethers.js et les constantes ABI/Adresse sont importés)

// 1. Initialiser le Provider à partir de Metamask (EIP-1193)
let provider;
let signer;
let daiContract;

async function connectWallet() {
  if (!window.ethereum) {
    alert("Veuillez installer Metamask !");
    return;
  }
  
  try {
    // 2. Créer le Provider
    provider = new ethers.providers.Web3Provider(window.ethereum);
    
    // 3. Demander la connexion (eth_requestAccounts)
    await provider.send("eth_requestAccounts", []);
    
    // 4. Obtenir le Signer (l'objet qui représente le compte connecté)
    signer = provider.getSigner();
    
    const address = await signer.getAddress();
    console.log(`Connecté avec l'adresse: ${address}`);

    // 5. Connecter le contrat au Signer (pour les transactions "write")
    // (daiContract était `new ethers.Contract(DAI_ADDRESS, DAI_ABI, provider)`)
    daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, signer);
    
  } catch (error) {
    console.error("Erreur de connexion:", error);
  }
}

// 6. Fonction pour ENVOYER une transaction (écriture)
async function sendDai(toAddress, amountString) {
  if (!daiContract || !signer) {
    alert("Veuillez d'abord connecter votre wallet.");
    return;
  }

  try {
    // 7. Convertir en unité (18 décimales)
    const amount = ethers.utils.parseUnits(amountString, 18);

    // 8. Appeler la fonction (Ethers sait qu'il doit .send() car le contrat est lié au Signer)
    // Metamask va s'ouvrir pour demander confirmation
    const tx = await daiContract.transfer(toAddress, amount);
    
    console.log(`Transaction envoyée ! Hash: ${tx.hash}`);

    // 9. (Optionnel) Attendre la confirmation (1 bloc)
    await tx.wait();
    console.log("Transaction confirmée !");

  } catch (error) {
    // L'utilisateur a rejeté la transaction, ou le Gas était trop bas, etc.
    console.error("Erreur de transaction:", error);
  }
}

// (Associer connectWallet() et sendDai() à des boutons dans le HTML)
4.1 Librairie : Web3.js (L'Original)

**Web3.js** est la bibliothèque JavaScript "historique" de l'écosystème Ethereum. Elle a été créée et est maintenue par la **Fondation Ethereum**. C'est un "monolithe" conçu pour être une boîte à outils complète, incluant l'interaction avec l'EVM (`web3.eth`), le réseau P2P (`web3.shh`), et des utilitaires (`web3.utils`).

Bien qu'Ethers.js soit aujourd'hui plus populaire pour les nouveaux projets, Web3.js est toujours activement maintenu (versions 1.x et 4.x) et est utilisé dans des milliers de projets existants.

Architecture & Syntaxe

Web3.js est structuré en modules attachés à l'objet principal `Web3`.

import Web3 from "web3";

// 1. Initialisation (avec un provider)
const web3 = new Web3("https://mainnet.infura.io/v3/YOUR_API_KEY");
// ou (avec Metamask)
// const web3 = new Web3(window.ethereum);

// 2. Accès aux modules
web3.eth.getBlockNumber().then(console.log);
web3.utils.toWei("1", "ether"); // "1000000000000000000"
  • web3.eth : Le module principal pour interagir avec la blockchain Ethereum (comptes, transactions, contrats).
  • web3.utils : Une collection d'utilitaires (ex: `toWei`, `fromWei`, `isAddress`).
  • web3.shh : (Aujourd'hui déprécié) Pour interagir avec le protocole de messagerie P2P "Whisper".
  • web3.bzz : (Aujourd'hui déprécié) Pour interagir avec le protocole de stockage "Swarm".
Gestion des Promesses (Historique)

Les premières versions de Web3.js (0.x) utilisaient des **callbacks**, ce qui menait au fameux "callback hell".

// Ancien Web3.js (0.x) - NE PLUS UTILISER
web3.eth.getBlockNumber(function(error, result) {
    if (!error) { console.log(result); }
});

Les versions modernes (1.x, 4.x) sont basées sur les **Promesses (Promises)**, ce qui permet d'utiliser `async/await` (similaire à Ethers.js).

// Web3.js Moderne (1.x / 4.x)
const blockNumber = await web3.eth.getBlockNumber();
Exemple : Lire des Données (Web3.js)

Lecture de données publiques (similaire à l'exemple Ethers).

import Web3 from "web3";

// 1. Définir le Provider (Infura)
const web3 = new Web3("https://mainnet.infura.io/v3/YOUR_API_KEY");

// 2. ABI et Adresse
const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const DAI_ABI = [ ... ABI JSON ... ]; // Web3.js attend l'objet JSON complet

// 3. Créer l'instance du Contrat
const daiContract = new web3.eth.Contract(DAI_ABI, DAI_ADDRESS);

// 4. Exécuter les appels (lecture)
async function readData() {
  try {
    const blockNumber = await web3.eth.getBlockNumber();
    console.log(`Block actuel: ${blockNumber}`);

    // Note : Web3.js n'a pas de support ENS natif dans le getBalance
    const vitalikBalance = await web3.eth.getBalance("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
    console.log(`Solde de Vitalik: ${web3.utils.fromWei(vitalikBalance, "ether")} ETH`);

    // 5. Appel de fonction "read" : .call()
    // Syntaxe spécifique à Web3.js
    const daiName = await daiContract.methods.name().call();
    console.log(`Nom du token: ${daiName}`);
    
    const someBalance = await daiContract.methods.balanceOf("0x...ADDRESS...").call();
    console.log(`Solde DAI: ${web3.utils.fromWei(someBalance, "ether")}`);
    
  } catch (error) {
    console.error("Erreur:", error);
  }
}

readData();
Exemple : Écrire des Données (.call vs .send)

C'est la différence syntaxique la plus marquante de Web3.js. L'interaction avec un contrat est toujours explicite :

  • .call() : Pour les lectures (fonctions `view`/`pure`). Simule l'appel (via `eth_call`). Ne coûte pas de Gas.
  • .send() : Pour les écritures (fonctions qui modifient l'état). Crée une transaction (via `eth_sendTransaction`). Coûte du Gas.
import Web3 from "web3";

// 1. Initialiser avec le Provider du Wallet (Metamask)
const web3 = new Web3(window.ethereum);

// (Instance de contrat... 'daiContract'...)

async function sendDai(toAddress, amountString) {
  try {
    // 2. Demander la connexion (Web3.js)
    const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
    const myAccount = accounts[0];

    // 3. Convertir en Wei
    const amount = web3.utils.toWei(amountString, "ether");

    // 4. Construire et ENVOYER la transaction : .send()
    // On doit spécifier le 'from' (l'expéditeur)
    const tx = await daiContract.methods.transfer(toAddress, amount).send({
      from: myAccount,
      // (on peut aussi spécifier gasLimit, gasPrice ici)
    });

    console.log(`Transaction envoyée ! Hash: ${tx.transactionHash}`);

    // Note : tx.wait() n'existe pas de la même manière.
    // Web3.js 1.x utilise un système d'événements pour écouter la confirmation.
    // .send() renvoie un "receipt" (reçu) une fois la transaction minée.

  } catch (error) {
    // L'utilisateur a rejeté, etc.
    console.error("Erreur de transaction:", error);
  }
}
5.1 Librairie : Ethers.js (Le Standard Moderne)

**Ethers.js** a été créé par Richard Moore (maintenant chez Optimism). Il a été conçu "from scratch" comme une alternative légère, complète et plus intuitive à Web3.js. Sa philosophie est basée sur une **séparation claire des responsabilités** (Provider, Signer, Contract), ce qui le rend conceptuellement plus propre et plus sûr.

Ethers.js (v5, et maintenant v6) est considéré comme le **standard de facto** pour le développement de nouvelles DApps aujourd'hui.

Philosophie : Les 3 Piliers d'Ethers
  1. Provider (Fournisseur) : Une connexion **lecture seule** à la blockchain (ex: Infura, Metamask).
  2. Signer (Signataire) : Un compte qui peut **autoriser/signer** des transactions (ex: Metamask, Ledger, clé privée).
  3. Contract (Contrat) : Un objet qui lie une Adresse, un ABI, et un (Provider *ou* Signer).
La Syntaxe "Magique" : .connect()

La grande élégance d'Ethers.js réside dans la façon dont il gère la lecture vs l'écriture. Il n'y a pas de `.call()` ou `.send()`.

import { ethers } from "ethers";

// 1. Initialiser le Provider (lecture seule)
const provider = new ethers.providers.Web3Provider(window.ethereum);

// 2. Obtenir le Signer (écriture)
const signer = provider.getSigner();

// 3. Instance de Contrat (LIÉE AU PROVIDER PAR DÉFAUT)
const contract_RO = new ethers.Contract(ADDRESS, ABI, provider); // RO = Read-Only

// 4. Instance de Contrat (LIÉE AU SIGNER)
// C'est la clé ! .connect() renvoie une NOUVELLE instance du contrat,
// attachée au signataire.
const contract_RW = contract_RO.connect(signer); // RW = Read-Write
Appel de Fonction Unifié

Ethers.js détermine *automatiquement* s'il doit faire un `eth_call` (lecture) ou `eth_sendTransaction` (écriture) en fonction de la *nature* de la fonction (view vs non-view) et de *l'objet* auquel le contrat est connecté.

// --- LECTURE ---
// (Fonction 'name()' est "view" dans l'ABI)
// Ethers voit "view" et sait qu'il doit faire un `eth_call` (gratuit).
const name = await contract_RO.name();

// (Fonction 'balanceOf(addr)' est "view")
const balance = await contract_RO.balanceOf("0x...123");


// --- ÉCRITURE ---
// (Fonction 'transfer(addr, val)' n'est PAS "view")
// Ethers voit "non-view" ET voit que l'objet (contract_RW) est lié à un Signer.
// Il sait qu'il doit construire et `eth_sendTransaction` (payant).
// Metamask s'ouvrira pour demander la signature.
const tx = await contract_RW.transfer("0x...456", ethers.utils.parseEther("1.0"));

// Le 'tx' renvoyé est un objet de réponse de transaction
await tx.wait(); // Attendre 1 confirmation

Cette syntaxe unifiée (appeler `myFunction()` dans les deux cas) est considérée comme beaucoup plus propre et moins sujette aux erreurs que la distinction `.call()`/`.send()` de Web3.js.

Autres Avantages
  • Support ENS Natif : Les noms ENS (Ethereum Name Service) sont des citoyens de première classe. `provider.getBalance("vitalik.eth")` fonctionne directement.
  • Légèreté : Ethers.js (v5) est beaucoup plus petit (léger) que Web3.js (1.x), ce qui est crucial pour la performance du frontend.
  • Typescript : Entièrement écrit en Typescript, offrant un typage robuste "out-of-the-box".
Exemple Complet (Ethers.js)

Ce code (dans un frontend) réalise le flux complet de lecture et d'écriture.

import { ethers } from "ethers";

// (ABI et ADRESSE du contrat définis ici)

class Web3Service {
    provider;
    signer;
    contract_RO;
    contract_RW;

    async connect() {
        if (!window.ethereum) throw new Error("Metamask non trouvé");

        // 1. Initialiser le Provider (lecture)
        this.provider = new ethers.providers.Web3Provider(window.ethereum);
        
        // 2. Demander la connexion
        await this.provider.send("eth_requestAccounts", []);
        
        // 3. Obtenir le Signer (écriture)
        this.signer = this.provider.getSigner();

        // 4. Créer l'instance de base (Read-Only)
        this.contract_RO = new ethers.Contract(CONTRACT_ADDRESS, ABI, this.provider);
        
        // 5. Créer l'instance connectée (Read-Write)
        this.contract_RW = this.contract_RO.connect(this.signer);

        const address = await this.signer.getAddress();
        console.log(`Connecté: ${address}`);
    }

    // Fonction de LECTURE
    async getMyBalance() {
        if (!this.contract_RO) throw new Error("Non connecté");
        
        const myAddress = await this.signer.getAddress();
        
        // Appel en lecture (gratuit) via le contrat 'RO' ou 'RW'
        // Ethers est assez intelligent pour savoir que .balanceOf() est 'view'
        const balance = await this.contract_RO.balanceOf(myAddress);
        
        return ethers.utils.formatUnits(balance, 18);
    }
    
    // Fonction d'ÉCRITURE
    async sendTokens(toAddress, amountString) {
        if (!this.contract_RW) throw new Error("Non connecté");
        
        try {
            const amount = ethers.utils.parseUnits(amountString, 18);
            
            // Appel en écriture (payant) via le contrat 'RW' (connecté au Signer)
            const tx = await this.contract_RW.transfer(toAddress, amount);
            
            console.log(`Tx envoyée: ${tx.hash}`);
            
            // Attendre la confirmation
            const receipt = await tx.wait();
            console.log(`Tx confirmée dans le bloc: ${receipt.blockNumber}`);
            
            return receipt;

        } catch (error) {
            console.error("Erreur de transaction:", error);
            // (ex: "user rejected transaction", "insufficient funds")
        }
    }
}
6.1 Comparaison Technique : Web3.js vs Ethers.js

Le choix entre Web3.js et Ethers.js était un débat majeur. Aujourd'hui (2025), Ethers.js est largement considéré comme le standard pour le développement de nouvelles applications, tandis que Web3.js reste crucial pour la maintenance des projets existants.

CritèreWeb3.js (v1.x / v4.x)Ethers.js (v5 / v6)
Auteur / MaintenanceFondation Ethereum (Jeff Wilcke, etc.).Richard Moore (financé par la communauté, maintenant chez Optimism).
Philosophie"Boîte à outils" monolithique. Vise à être une implémentation complète de l'API Ethereum."Bibliothèque" légère et modulaire. Se concentre sur la simplicité et l'abstraction (Provider/Signer).
Taille (Bundle Size)Très Lourd. Web3.js (1.x) peut peser plusieurs centaines de Ko (gzippé), ce qui impacte le chargement du frontend.Très Léger. Ethers.js (v5) est ~130 Ko (gzippé), conçu pour être "tree-shakeable". (v6 est encore plus modulaire).
ArchitectureObjet `Web3` centralisé avec des modules (web3.eth, web3.utils).Séparation claire des responsabilités : Provider (lecture), Signer (écriture), Contract (interface).
Interaction ContratSyntaxe explicite et verbeuse :
myContract.methods.myFunc().call() (Lecture)
myContract.methods.myFunc().send() (Écriture)
Syntaxe intuitive et unifiée :
contract.myFunc() (Lecture, si `view` ou lié au Provider)
contract.myFunc() (Écriture, si non-`view` et lié au Signer)
Gestion des TypesGestion des "Big Numbers" via web3.utils (basé sur BN.js).Gestion native des BigNumber (via ethers.BigNumber). Considéré comme plus robuste et simple.
Support ENSSupport basique. Nécessite souvent une configuration ou une bibliothèque supplémentaire.Support ENS Natif. `provider.getBalance("vitalik.eth")` et `provider.resolveName("vitalik.eth")` fonctionnent "out-of-the-box".
DocumentationDense et complète, mais peut être difficile à naviguer pour les débutants.Largement considérée comme plus propre, plus simple et mieux structurée.
Courbe d'ApprentissageFacile au début, mais la gestion des `send` vs `call` et des promesses peut être confuse.La *philosophie* (Provider/Signer) demande un léger temps d'adaptation, mais une fois comprise, elle est beaucoup plus intuitive et puissante.
Conclusion (Recommandation 2025)
Pour tout nouveau projet (DApp, script), utilisez Ethers.js.
Sa légèreté, son architecture (Provider/Signer), son support natif ENS et sa syntaxe de contrat unifiée en font un choix supérieur en termes de maintenabilité et d'expérience développeur (DX).
Utilisez Web3.js si...
Vous maintenez un projet existant (ex: un codebase de 2019) qui en dépend, ou si vous utilisez des outils (ex: Truffle, bien que Ganache/Truffle supportent Ethers) qui sont historiquement liés à Web3.js.