Inicio / Blockchain / Blockchain: De Cero a Desarrollador / ERC-20: Tokens Fungibles

ERC-20: Tokens Fungibles

Interfaz ERC-20, approve/transferFrom, mint, burn y OpenZeppelin.

Intermedio Funciones
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

ERC-20: Tokens Fungibles

ERC-20 es el estándar más utilizado en Ethereum para crear tokens fungibles. Desde stablecoins como USDT y USDC hasta governance tokens como UNI y AAVE, la mayoría de los tokens siguen este estándar.

¿Qué es un Token Fungible?

Un token fungible es un activo digital donde cada unidad es idéntica e intercambiable con cualquier otra:

Fungible (ERC-20):              No Fungible (ERC-721):
- 1 USDT = 1 USDT              - NFT #1 ≠ NFT #2
- 1 UNI = 1 UNI                - Cada uno es único
- Divisible                     - Indivisible
- Intercambiable                - Coleccionable

La Interfaz ERC-20

El estándar define las funciones y eventos mínimos que un token debe implementar:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    // Consultas
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);

    // Transferencias
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

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

Implementación Completa

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract MiToken is IERC20 {
    string public name;
    string public symbol;
    uint8 public decimals = 18;
    uint256 private _totalSupply;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    constructor(string memory _name, string memory _symbol, uint256 _supply) {
        name = _name;
        symbol = _symbol;
        _totalSupply = _supply * 10 ** decimals;
        _balances[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
    }

    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) external view returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        require(to != address(0), "Transfer to zero address");
        require(_balances[msg.sender] >= amount, "Insufficient balance");

        _balances[msg.sender] -= amount;
        _balances[to] += amount;

        emit Transfer(msg.sender, to, amount);
        return true;
    }

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

    function allowance(address owner, address spender) external view returns (uint256) {
        return _allowances[owner][spender];
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        require(to != address(0), "Transfer to zero address");
        require(_balances[from] >= amount, "Insufficient balance");
        require(_allowances[from][msg.sender] >= amount, "Insufficient allowance");

        _allowances[from][msg.sender] -= amount;
        _balances[from] -= amount;
        _balances[to] += amount;

        emit Transfer(from, to, amount);
        return true;
    }
}

El Flujo Approve + TransferFrom

Este mecanismo permite que contratos terceros muevan tokens en tu nombre:

Escenario: Alice quiere intercambiar tokens en Uniswap

1. Alice llama: token.approve(uniswapRouter, 1000)
   → "Uniswap puede mover hasta 1000 de mis tokens"

2. Alice llama: uniswap.swap(token, 1000)
   → Uniswap internamente: token.transferFrom(alice, pool, 1000)
   → Uniswap mueve los tokens de Alice al pool

Sin approve + transferFrom, los contratos no podrían
interactuar con tus tokens de forma segura.

Decimales

Los tokens ERC-20 no soportan decimales nativamente. Se usa un multiplicador:

Si decimals = 18:
- 1 token   = 1000000000000000000 (10^18)
- 0.5 token = 500000000000000000

Si decimals = 6 (como USDC):
- 1 USDC    = 1000000
- 0.01 USDC = 10000

En el código siempre trabajamos con enteros (Wei-like).
El frontend convierte para mostrar al usuario.

Extensiones Comunes

Token con Mint y Burn

contract TokenConMintBurn is MiToken {
    address public owner;

    constructor() MiToken("Mi Token", "MTK", 1000000) {
        owner = msg.sender;
    }

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

    function burn(uint256 amount) external {
        require(_balances[msg.sender] >= amount, "Insufficient balance");
        _balances[msg.sender] -= amount;
        _totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }

    mapping(address => uint256) internal _balances;
    uint256 internal _totalSupply;
}

Token con Pausa

contract TokenPausable {
    bool public paused;
    address public owner;

    modifier whenNotPaused() {
        require(!paused, "Token pausado");
        _;
    }

    function pause() external {
        require(msg.sender == owner);
        paused = true;
    }

    function unpause() external {
        require(msg.sender == owner);
        paused = false;
    }

    // Agregar whenNotPaused a transfer y transferFrom
}

Usando OpenZeppelin

En la práctica, siempre usamos OpenZeppelin para tokens ERC-20:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MiTokenPro is ERC20, ERC20Burnable, Ownable {
    constructor()
        ERC20("Mi Token Pro", "MTPRO")
        Ownable(msg.sender)
    {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

Tokens Famosos ERC-20

Token     Símbolo    Decimales    Uso
──────────────────────────────────────────────
USDT      USDT       6           Stablecoin (Tether)
USDC      USDC       6           Stablecoin (Circle)
DAI       DAI        18          Stablecoin descentralizada
UNI       UNI        18          Governance de Uniswap
LINK      LINK       18          Oracles de Chainlink
AAVE      AAVE       18          Governance de Aave
WETH      WETH       18          ETH wrapeado

Resumen

ERC-20 define la interfaz estándar para tokens fungibles en Ethereum: balanceOf, transfer, approve, transferFrom y los eventos Transfer y Approval. El patrón approve-transferFrom permite que contratos muevan tokens con autorización. En producción, siempre usar OpenZeppelin para implementaciones probadas y auditadas.

🔒

Ejercicio práctico disponible

Implementar token ERC-20 desde cero

Desbloquear ejercicios
// Implementar token ERC-20 desde cero
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes