Ethereum et smart contracts · 6 min · démo interactive

Créer un token ERC-20 : le code ligne par ligne

Un token n'est pas une pièce qui circule. C'est une table de soldes dans un contrat, que tu vas modifier ici instruction par instruction.

L’idée en une phrase

Un token ERC-20 est une ligne dans le tableau de soldes d'un contrat : transférer, c'est demander à ce contrat de retirer un nombre d'une case pour l'ajouter à une autre.

Rien ne quitte ton wallet au sens physique. ERC-20 est une norme d'interface publiée en 2015 : un petit ensemble de fonctions et d'événements. Tout contrat qui les respecte peut être affiché par les wallets et échangé par les applications, sans adaptation particulière.

La démo

–totalSupply, en tokens (18 décimales)
ComptebalanceOfEmplacement de stockage
Alice750–
Bob250–
Carole0–
Aucune autorisation en cours.
Les écritures de stockage du dernier appel s'affichent ici.

Journal des appels et des événements

  1. Le journal se remplit au chargement.

Sans JavaScript, voici l'état de départ : Alice frappe 1 000 tokens, puis en envoie 250 à Bob. L'offre totale reste 1 000, Alice détient 750 et Bob 250. Comme le token a 18 décimales, le contrat stocke en réalité 750 × 1018 unités pour Alice : aucun nombre à virgule n'existe sur l'EVM.

Le contrat tourne dans notre mini-EVM pédagogique, un sous-ensemble du vrai jeu d'instructions : c'est du bytecode assemblé à la main, pas un modèle en JavaScript. Le token n'existe que dans cette page, n'a aucune valeur et disparaît au rechargement. Le gas est indicatif.

Intégrer cette démo

Copie ce code sur ton site, ton cours ou ton blog. La démo reste gratuite ; garde simplement le lien vers protocole.org.

Pourquoi ça compte

Une association veut émettre 1 000 jetons de vote. Créer une blockchain serait absurde. Il suffit d'un contrat de quelques dizaines de lignes : la frappe, le transfert et les autorisations tiennent dans une poignée de fonctions. C'est pour cela que des tokens se créent chaque jour, et pour cela aussi que le ticket d'entrée d'un projet frauduleux est si bas.

Comprendre ce code, c'est savoir ce que tu détiens quand un wallet affiche un solde : une écriture dans un contrat que quelqu'un a écrit, avec ses règles. Le contrat peut prévoir une frappe illimitée pour son propriétaire. Le nom et le logo, eux, ne prouvent rien.

Le code

Ce panneau rejoue la mécanique des emplacements de stockage sur un jeton réduit à balanceOf et transfer. Change le montant envoyé à Bob, puis mets-le au-dessus de 1000 : le contrat refuse.

Clique sur Lancer pour exécuter ce code dans ton navigateur.

Ce qui se passe vraiment

Voici l'équivalent simplifié en Solidity du contrat de la démo. Le bytecode a été assemblé à la main, il n'est donc pas la sortie exacte du compilateur. Nous avons rejoué les mêmes appels sur les deux versions dans une EVM de référence : réponses, événements, emplacements et valeurs de stockage concordent.

contract Jeton {
    uint256 public totalSupply;                                       // emplacement 0
    mapping(address => uint256) public balanceOf;                     // emplacement 1
    mapping(address => mapping(address => uint256)) public allowance; // emplacement 2
    address public owner;                                             // emplacement 3
    uint8 public constant decimals = 18;

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

    constructor() { owner = msg.sender; }

    function mint(address to, uint256 amount) external {
        require(msg.sender == owner);
        totalSupply += amount;
        balanceOf[to] += amount;
        emit Transfer(address(0), to, amount);
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        _move(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        require(allowance[from][msg.sender] >= amount);
        allowance[from][msg.sender] -= amount;
        _move(from, to, amount);
        return true;
    }

    function _move(address from, address to, uint256 amount) internal {
        require(balanceOf[from] >= amount);
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
    }
}
  • balanceOf. Un mapping n'est pas un tableau parcourable. Le solde d'Alice se range à l'emplacement keccak256(adresse . 1), un nombre de 256 bits sans voisin. C'est la colonne « emplacement » de la démo. Conséquence : un contrat ne sait pas lister ses détenteurs. Ce sont les événements qui permettent de reconstruire cette liste.
  • mint. Il augmente l'offre et un solde. Lance-le avec Bob comme signataire : REVERT, car seule Alice est propriétaire. L'événement Transfer part de l'adresse zéro, c'est la convention qui signale une frappe. Cette fonction n'appartient pas à la norme ERC-20.
  • transfer. Il lit, vérifie, soustrait, ajoute, puis émet un événement. Tente d'envoyer plus que ton solde : le journal indique REVERT et rien n'est modifié.
  • approve puis transferFrom. C'est le mécanisme d'autorisation. Alice autorise Bob à dépenser 100 tokens, puis Bob déclenche transferFrom avec Alice comme compte débité. Tout service qui doit prélever tes tokens, comme un pool d'échange, passe par là.

Le signataire compte. Il correspond à msg.sender, une valeur fixée par le protocole d'après la signature de la transaction : le contrat n'a pas à la croire sur parole. Dans la démo, il suffit de choisir un nom, car rien n'est signé.

Ce contrat de démonstration simplifie la norme : pas de name ni de symbol, et aucune validation de la forme des adresses reçues. Ne le prends pas pour un modèle à déployer.

Dans la vraie vie

En production, on n'écrit pas ce code soi-même. On hérite d'implémentations auditées, comme celle d'OpenZeppelin, qui ajoutent la gestion des cas limites. La création technique d'un token prend quelques minutes. Ce qui prend du temps, c'est tout le reste : rôles d'administration, distribution, audit et cadre juridique. En Europe, proposer un token au public peut relever du règlement MiCA.

Un transfert classique coûte plus cher qu'un envoi d'ETH, car il exécute du code et écrit dans le stockage. Dans la démo, le premier envoi vers un compte vide reflète les 20 000 gas de la création d'un emplacement. Le prix réel dépend du marché du gas.

Les soldes n'existent que sur la chaîne du contrat. Un même token affiché sur deux réseaux, ce sont deux contrats distincts, reliés au mieux par un bridge. Le stablecoin que tu vois sur une autre chaîne n'est pas la même écriture.

Ce qui peut mal tourner

  • Approbations illimitées. Un approve vers un contrat malveillant suffit à vider ce token de ton wallet, sans autre signature. Voir les approbations de tokens.
  • Faux tokens. N'importe qui peut déployer un contrat nommé comme un actif connu. Seule l'adresse du contrat identifie un token, jamais son symbole.
  • Frappe et gel cachés. Un propriétaire peut garder le droit de frapper sans limite, de bloquer des adresses ou de modifier des frais de transfert. Le contrat de la démo a lui aussi une frappe sans plafond pour Alice.
  • Tokens pièges. Certains contrats acceptent les achats et font échouer les ventes par une condition dans _move. La logique du transfert est libre, la norme ne fixe que l'interface.
  • Tokens envoyés à un contrat. transfer ne prévient pas le destinataire. Un contrat qui ne sait pas les traiter les garde pour toujours : des montants importants sont bloqués ainsi.
  • Décimales mal lues. Toutes les valeurs sont entières. Oublier les 18 décimales, ou supposer qu'un autre token en a 18 alors qu'il en a 6, fausse un montant d'un facteur énorme.

Teste-toi

1. Où se trouve réellement ton solde d'un token ERC-20 ?
2. À quoi sert approve ?
3. Comment reconnaître avec certitude un token ?