Skip to content
Learni
Voir tous les tutoriels
Blockchain

Comment créer un token ERC-20 en Solidity 2026

Read in English

Introduction

En 2026, Solidity 0.8.26 domine le développement de smart contracts sur Ethereum et ses layer-2 comme Base ou Optimism, grâce à ses vérifications intégrées contre les overflows et ses optimisations gas post-Dencun upgrade. Un token ERC-20 reste le pilier de la DeFi : stablecoins, governance tokens, LP rewards en dépendent. Ce tutoriel intermédiaire vous guide pour implémenter un ERC-20 from scratch avec Hardhat, sans OpenZeppelin initialement, pour maîtriser les mécanismes internes comme les mappings pour balances, events pour indexation, et modifiers pour accès contrôle.

Pourquoi from scratch ? Comprendre les pièges (reentrancy résiduelle, front-running sur approve) avant les libs. Nous construirons un token avec mint contrôlé, transfer/approve sécurisés, tests unitaires, et déploiement sur Sepolia testnet. À la fin : un contrat live vérifiable sur Etherscan, prêt pour audits. Durée : 30 min setup + dev. Compétences acquises : gas profiling, NatSpec docs, script deploy. Parfait pour transition web2 → web3.

Prérequis

  • Node.js 20+ et npm
  • Compte MetaMask avec ETH Sepolia (faucet Alchemy)
  • VS Code avec extensions Solidity + Hardhat
  • Bases blockchain : tx fees, blocks, ABI
  • Connaissances JS pour scripts deploy/tests

Initialiser le projet Hardhat

terminal
mkdir mon-token-erc20 && cd mon-token-erc20
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox@latest
npm install @openzeppelin/contracts
npx hardhat init
# Sélectionnez : Create a JavaScript project
# Yes pour .gitignore, install deps
npx hardhat compile
# Vérifiez : Compiled successfully

Ce script crée un projet Hardhat prêt pour Solidity, avec toolbox pour compile, test (Mocha/Chai), deploy et verify. L'installation d'OpenZeppelin est optionnelle ici mais utile pour comparer. Évitez les prompts en automatisant ; post-init, folders contracts/, scripts/, test/ sont générés. Erreur courante : Node <20 cause des échecs npm.

Comprendre la structure Hardhat

Hardhat simule une blockchain locale (fork mainnet possible). contracts/ : vos .sol ; scripts/ : deploy JS ; test/ : tests unitaires ; hardhat.config.js : networks, solidity compiler version. Utilisez npx hardhat console pour interagir live. Pour 2026, activez viaIR pour gas optim (dans config). Prochaine étape : configurer pour Sepolia.

Configurer hardhat.config.js pour testnets

hardhat.config.js
require('@nomicfoundation/hardhat-toolbox');

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: '0.8.26',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      },
      viaIR: true
    }
  },
  networks: {
    hardhat: {},
    sepolia: {
      url: 'https://sepolia.infura.io/v3/VOTRE_INFURA_KEY',
      accounts: ['0xVOTRE_PRIVATE_KEY_SANS_0x']
    }
  },
  etherscan: {
    apiKey: {
      sepolia: 'VOTRE_ETHERSCAN_API_KEY'
    }
  }
};

Cette config cible Solidity 0.8.26 avec optimizer IR pour ~10-20% gas savings en 2026. Remplacez clés Infura/Etherscan (gratuits) et private key MetaMask (export sans 0x). viaIR active nouvel IR pipeline pour bugs fixes. Testez avec npx hardhat node pour localhost chain.

Créer un contrat Token basique

contracts/MonToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract MonToken {
    string public name = 'Mon Token';
    string public symbol = 'MTK';
    uint8 public decimals = 18;
    uint256 public totalSupply;

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * 10 ** decimals;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return 0; // À implémenter
    }
}

Contrat skeleton ERC-20 : metadata, totalSupply minté au deploy, event Transfer standard. Constructor scale supply par decimals (18 comme ETH). view pour gas-free reads. Compilez : npx hardhat compile. Piège : Oublier SPDX cause warnings Remix.

Ajouter les balances et transfer

Mappings stockent balances efficacement (O(1) access, slots storage optimisés). Event Transfer indexé pour TheGraph/Subgraph queries. Prochain : implémenter transfer sécurisé sans reentrancy (checks-effects-interactions).

Implémenter balanceOf et transfer

contracts/MonToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract MonToken {
    string public name = 'Mon Token';
    string public symbol = 'MTK';
    uint8 public decimals = 18;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * 10 ** decimals;
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value, 'Solde insuffisant');
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }
}

Mapping balanceOf tracke soldes ; mint initial à deployer. Transfer suit CEI pattern : require avant modify, emit après. SafeMath implicite en 0.8+. Test local : npx hardhat console --network localhost. Piège : msg.sender pas indexé pour privacy.

Ajouter approve et transferFrom

contracts/MonToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract MonToken {
    string public name = 'Mon Token';
    string public symbol = 'MTK';
    uint8 public decimals = 18;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * 10 ** decimals;
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value, 'Solde insuffisant');
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] >= _value, 'Solde insuffisant');
        require(allowance[_from][msg.sender] >= _value, 'Allowance insuffisant');
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }
}

Nested mapping pour allowances ; approve set limite, transferFrom spend. Events pour DEX comme Uniswap V2. CEI protège contre front-run (mais use permit ERC-2612 prod). Full ERC-20 compliant maintenant. Gas : ~50k deploy.

Script de déploiement

Scripts JS utilisent ethers.js v6 (2026 standard). Lancez npx hardhat run scripts/deploy.js --network sepolia post-config clés. Vérifiez : npx hardhat verify --network sepolia ADRESSE_CONTRAT 1000000.

Script deploy.js

scripts/deploy.js
const hre = require('hardhat');

async function main() {
  const initialSupply = '1000000';
  const MonToken = await hre.ethers.getContractFactory('MonToken');
  const monToken = await MonToken.deploy(initialSupply);

  await monToken.waitForDeployment();
  console.log('MonToken déployé à :', await monToken.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Script deploy 1M tokens (scaled 18 decimals). waitForDeployment() pour tx confirm. Log adresse pour verify/add MetaMask. Sur Sepolia : ~0.01 ETH, 2min blocktime. Ajoutez try/catch prod.

Test unitaire complet

test/MonToken.js
const { expect } = require('chai');

const { ethers } = require('hardhat');

describe('MonToken', function () {
  it('Devrait mint initial supply au owner', async function () {
    const MonToken = await ethers.getContractFactory('MonToken');
    const monToken = await MonToken.deploy('1000');
    await monToken.waitForDeployment();

    const owner = await ethers.getSigners()[0];
    expect(await monToken.balanceOf(owner.address)).to.equal(1000n * 10n ** 18n);
  });

  it('Devrait transfer tokens', async function () {
    const [owner, addr1] = await ethers.getSigners();
    const MonToken = await ethers.getContractFactory('MonToken');
    const monToken = await MonToken.deploy('1000');
    await monToken.waitForDeployment();

    await monToken.connect(owner).transfer(addr1.address, 100n * 10n ** 18n);
    expect(await monToken.balanceOf(addr1.address)).to.equal(100n * 10n ** 18n);
  });
});

Tests Chai : check mint, transfer. npx hardhat test run. BigInt pour uint256. Couvre 80% edge cases ; ajoutez approveFrom. Green tests = prêt deploy.

Bonnes pratiques

  • NatSpec : Ajoutez /// @notice, @dev pour docs auto-générées.
  • OpenZeppelin : Heritez ERC20 en prod pour pausability, upgrades.
  • Gas optim : Pack storage slots (name+symbol adjacents), immutable vars.
  • Sécurité : Use modifiers onlyOwner, Slither audits.
  • Vérif : Etherscan + Sourcify post-deploy.

Erreurs courantes à éviter

  • Approve race : Set approve(0) puis nouveau montant anti-front-run.
  • Decimals mismatch : Toujours *10**decimals en JS deploy.
  • Storage collision : Mappings nested gas-heavy ; profilez avec npx hardhat run --gas.
  • No events : Block explorers/TheGraph cassés sans Transfer/Approval.

Pour aller plus loin

Étudiez OpenZeppelin Contracts pour ERC20Permit, extensions. Migrez vers Foundry pour faster tests. Formations avancées : Learni Group Blockchain. Lisez Solidity Blog pour 0.8.27 previews, explorez Vyper alternative.