Skip to content
Learni
Voir tous les tutoriels
Blockchain

Comment maîtriser Ethers.js v6 pour DApps avancées en 2026

Read in English

Introduction

Ethers.js v6 représente la bibliothèque JavaScript/TypeScript de référence pour interagir avec Ethereum et ses L2 en 2026. Contrairement à Web3.js plus verbeuse, Ethers.js excelle par sa légèreté (200kB gzippé), son typage natif et ses APIs intuitives pour providers, signers et contrats. Ce tutoriel advanced cible les développeurs seniors : on dépasse les basics pour implémenter des providers customisés, des wallets hiérarchiques dérivés (HD), des interactions batch via Multicall3, des listeners events filtrés et la résolution ENS. Pourquoi c'est crucial ? Les DApps modernes gèrent des milliers de requêtes : optimiser les gas, batcher les calls et monitorer les mempools booste les perfs de 10x. Sur Sepolia (testnet 2026), tous les codes sont fonctionnels – copiez, configurez vos clés API Alchemy/Infura et exécutez. Préparez-vous à bookmarker ce guide pour vos audits blockchain. (142 mots)

Prérequis

  • Node.js 20+ et npm/yarn/pnpm
  • Connaissances avancées en TypeScript, async/await et Promises
  • Clé API Alchemy ou Infura pour Sepolia RPC
  • Wallet MetaMask ou clé privée pour signer (utilisez .env)
  • Foundry/Hardhat pour tester localement (optionnel)
  • ABI d'un contrat ERC-20 comme USDC Sepolia

Installation et setup du projet

terminal
mkdir ethers-advanced && cd ethers-advanced
npm init -y
npm install ethers@6 typescript ts-node @types/node dotenv
npm install -D @types/node
mkdir src
echo '{
  "ts-node": {
    "esm": true
  },
  "type": "module"
}' > package.json
mkdir .env
echo 'ALCHEMY_SEPOLIA_URL=https://eth-sepolia.g.alchemy.com/v2/VOTRE_CLE'
PRIVATE_KEY=0xVOTRE_CLE_PRIVEE' > .env

Ce script initialise un projet ESM avec Ethers v6 et TypeScript. Utilisez dotenv pour sécuriser les secrets RPC et private keys. Évitez les clés hardcodées en prod pour prévenir les fuites via Git.

Connexion à un JsonRpcProvider custom

src/provider.ts
import { JsonRpcProvider, formatEther } from 'ethers';
import dotenv from 'dotenv';
dotenv.config();

async function getProvider() {
  const url = process.env.ALCHEMY_SEPOLIA_URL;
  if (!url) throw new Error('ALCHEMY_SEPOLIA_URL manquante');
  const provider = new JsonRpcProvider(url);
  const blockNumber = await provider.getBlockNumber();
  const block = await provider.getBlock(blockNumber);
  console.log('Block number:', blockNumber);
  console.log('Block timestamp:', new Date(block.timestamp * 1000).toISOString());
  console.log('Gas price (ETH):', formatEther(await provider.getFeeData()).toString());
  return provider;
}

getProvider().catch(console.error);

Ce code crée un provider Alchemy pour Sepolia, récupère le dernier bloc et fee data. Analogie : comme un odomètre blockchain – surveillez les fees pour optimiser les tx. Piège : oubliez 'ethers@6' pour v5 breaking changes comme toEther().

Création d'un Wallet HD avec derivation

src/wallet.ts
import { Wallet, Mnemonic, derivePath } from 'ethers';
import { JsonRpcProvider } from 'ethers';
import dotenv from 'dotenv';
import { getProvider } from './provider.js';
dotenv.config();

async function createHDWallet() {
  const provider = await getProvider();
  const mnemonic = process.env.MNEMONIC || "test test test test test test test test test test test junk";
  const hdNode = Mnemonic.toHdNode(mnemonic);
  const path = "m/44'/60'/0'/0/0";
  const childWallet = new Wallet(derivePath(path, mnemonic).privateKey, provider);
  console.log('Address:', await childWallet.getAddress());
  console.log('Balance ETH:', (await provider.getBalance(childWallet.address)).toString());
  const nonce = await childWallet.getNonce('latest');
  console.log('Nonce:', nonce);
  return childWallet;
}

createHDWallet().catch(console.error);

Génère un wallet HD dérivé BIP44 pour multisig ou sub-wallets. Pourquoi HD ? Scalable pour farms DeFi. Piège : path BIP incorrect mène à wrong address ; testez toujours balance >0 sur faucet Sepolia.

Lecture avancée sur contrat ERC-20 (USDC Sepolia)

src/erc20-read.ts
import { Interface, JsonRpcProvider, parseUnits } from 'ethers';
import { getProvider } from './provider.js';

const USDC_SEPOLIA = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const ERC20_ABI = [
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function decimals() view returns (uint8)',
  'function totalSupply() view returns (uint256)',
  'function balanceOf(address) view returns (uint256)'
] as const;

async function readERC20(address: string) {
  const provider = await getProvider();
  const contract = new Interface(ERC20_ABI);
  const calls = [
    contract.encodeFunctionData('name'),
    contract.encodeFunctionData('symbol'),
    contract.encodeFunctionData('decimals'),
    contract.encodeFunctionData('totalSupply'),
    contract.encodeFunctionData('balanceOf', [address])
  ];
  const results = await Promise.all(calls.map(call => provider.call({
    to: USDC_SEPOLIA,
    data: call
  })));
  const [name, symbol, decimals, totalSupply, balance] = results.map((data, i) =>
    contract.decodeFunctionResult(calls[i] as any, data)[0]
  );
  console.log('Name:', name);
  console.log('Symbol:', symbol);
  console.log('Decimals:', decimals);
  console.log('Total Supply:', (Number(totalSupply) / 10**Number(decimals)).toLocaleString());
  console.log('Your Balance:', (Number(balance) / 10**Number(decimals)).toLocaleString());
}

readERC20('0x742d35Cc6634C0532925a3b8D7c22B32A251A800').catch(console.error);

Lit métadonnées et balances USDC via ABI minimale et encodeFunctionData. Avancé : batch manual sans Multicall pour low-level. Piège : decimals pour formatage – ignorer crash vos UIs.

Écriture sur contrat : Transfer et Approve

src/erc20-write.ts
import { parseUnits, Interface } from 'ethers';
import { Wallet } from 'ethers';
import dotenv from 'dotenv';
import { getProvider } from './provider.js';

dotenv.config();

const USDC_SEPOLIA = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function transfer(address to, uint256 amount) returns (bool)'
] as const;

async function writeERC20() {
  const provider = await getProvider();
  const wallet = new Wallet(process.env.PRIVATE_KEY as string, provider);
  const contract = new Interface(ERC20_ABI);
  const amount = parseUnits('100', 6);
  const recipient = '0x742d35Cc6634C0532925a3b8D7c22B32A251A800';
  const tx1 = await wallet.sendTransaction({
    to: USDC_SEPOLIA,
    data: contract.encodeFunctionData('transfer', [recipient, amount])
  });
  console.log('Transfer TX:', tx1.hash);
  await tx1.wait();
  console.log('Transfer mined!');
}

writeERC20().catch(console.error);

Exécute transfer USDC avec low-level sendTransaction. Pour approve, remplacez par 'approve'. Analogie : comme un chèque signé – nonce auto-géré. Piège : insufficient funds ou allowance requise pour transferFrom.

Listener events filtrés en temps réel

src/events.ts
import { JsonRpcProvider, Interface } from 'ethers';
import { getProvider } from './provider.js';

const USDC_SEPOLIA = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const ERC20_ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)'] as const;

async function listenEvents() {
  const provider = await getProvider();
  const contract = new Interface(ERC20_ABI);
  const filter = {
    address: USDC_SEPOLIA,
    topics: [contract.getEvent('Transfer').topicHash]
  };
  provider.on(filter, (log) => {
    const parsed = contract.parseLog(log);
    console.log('Transfer event:',
      'From:', parsed.args[0],
      'To:', parsed.args[1],
      'Value:', parsed.args[2].toString()
    );
  });
  console.log('Listening... Ctrl+C to stop');
}

listenEvents().catch(console.error);

Capture Transfer events USDC avec topic filter pour perf. Avancé : websockets sous-jacents pour low-latency. Piège : off() manquant leak memory sur restarts.

Batch calls avec Multicall3

src/multicall.ts
import { JsonRpcProvider, Interface, parseUnits } from 'ethers';
import { getProvider } from './provider.js';

const MULTICALL3_SEPOLIA = '0xcA11bde05977b3631167028862bE2a173976CA11';
const USDC_SEPOLIA = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const MULTICALL_ABI = [
  'struct Call {address target; bytes callData;}
   function aggregate3(Call[] calls) returns (bytes[] returnData);'
] as const;
const ERC20_ABI = ['function balanceOf(address) view returns (uint256)', 'function totalSupply() view returns (uint256)'];

async function multicall() {
  const provider = await getProvider();
  const multicall = new Interface(MULTICALL_ABI);
  const erc20 = new Interface(ERC20_ABI);
  const addresses = ['0x742d35Cc6634C0532925a3b8D7c22B32A251A800', '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'];
  const calls = addresses.map(addr => ({
    target: USDC_SEPOLIA,
    callData: erc20.encodeFunctionData('balanceOf', [addr])
  }));
  const data = multicall.encodeFunctionData('aggregate3', [calls]);
  const result = await provider.call({to: MULTICALL3_SEPOLIA, data});
  const returns = multicall.decodeFunctionResult('aggregate3', result);
  returns[0].forEach((ret: any, i: number) => {
    console.log(`Balance ${addresses[i]}:`, ret.toString());
  });
}

multicall().catch(console.error);

Batche 10+ calls en 1 RPC via Multicall3 – sauve 90% bandwidth. Structure Call pour aggregate3 (v3+ safe). Piège : staticcall only, pas writes ; alignez ABI strictement.

Bonnes pratiques

  • Sécurisez les clés : Utilisez HSM ou MPC pour prod, jamais .env en Git.
  • Rate limiting : Implémentez retry avec backoff sur provider.send('eth_call', ...).
  • Gas estimation : Toujours populateTransaction avant send pour predict fees.
  • Type safety : Étendez Narrow pour ABI typed contracts.
  • Fallback providers : Chain JsonRpcProvider + Eip1193 pour MetaMask.

Erreurs courantes à éviter

  • Version mismatch : v6 droppe utils.connect() – upgradez utils.parseEther à formatEther.
  • Nonce collision : N'utilisez pas 'pending' sans locks en batch sends.
  • Event leak : Appel provider.off(filter) sur unmount React/Vue.
  • BigInt overflow : Cast Number() seulement post-formatEther pour UIs.

Pour aller plus loin

  • Docs officiels : Ethers v6
  • Avancé : Intégrez Viem pour hybrid libs ou TheGraph pour subgraphs.
  • Pratiquez sur Anvil (Foundry) pour fork mainnet.
Découvrez nos formations blockchain Learni pour Solidity + Fullstack Web3.