ERC20 - Token Fungível
Implementação didática do padrão ERC-20 para criar suas próprias criptomoedas.
Conceitos ensinados:
- transfer() e transferFrom() — enviar tokens
- balanceOf() — saldo de um endereço
- approve() e allowance() — autorização delegados
- Events (Transfer, Approval)
- Decimals e supply total
- Mint e Burn
Código
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title MeuPrimeiroToken
* @dev Implementação didática do padrão ERC-20
*
* ERC-20 é o padrão de tokens fungíveis no Ethereum.
* "Fungível" significa que todos os tokens são idênticos e
* intercambiáveis — assim como dinheiro. R$10 é R$10,
* independente de qual nota você tem na mão.
*
* Diferença principal do ERC-721:
* - ERC-721: cada token tem um ID único e um dono
* - ERC-20: tokens não têm identidade, só quantidade
*
* Como testar no Remix:
* 1. Deploy passando _initialSupply (ex: 1000).
* 2. Chame balanceOf(conta1) e confira o supply inicial.
* 3. Chame transfer(conta2, valor) e valide saldo de conta2.
* 4. Conta2 chama approve(conta3, valor) e conta3 usa transferFrom(conta2, conta1, valor).
* 5. Teste mint(conta2, valor) e burn(valor) para validar totalSupply.
*/
contract MeuPrimeiroToken {
// =========================================================
// DADOS DO TOKEN
// =========================================================
string public name = "Aula Bruno Token";
string public symbol = "ABT";
// Casas decimais: define a menor fração possível do token.
// 18 é o padrão — igual ao Ether (1 ETH = 10^18 wei).
// Exemplo: 1 ABT "humano" = 1000000000000000000 na blockchain.
uint8 public decimals = 18;
// Quantidade total de tokens em circulação.
// Aumenta no mint, diminui no burn.
uint256 public totalSupply;
// =========================================================
// MAPPINGS PRINCIPAIS
// =========================================================
// Dado um endereço, qual é o seu saldo?
mapping(address => uint256) private _balances;
// Dado um dono e um gastador, quanto o gastador pode gastar?
// Isso é a "allowance" — permissão de gasto delegado.
// Usado por contratos DeFi, exchanges etc.
mapping(address => mapping(address => uint256)) private _allowances;
// =========================================================
// EVENTOS (padrão ERC-20 obrigatório)
// =========================================================
// Emitido sempre que tokens mudam de endereço.
// No mint: _from = address(0), pois os tokens não existiam antes.
// No burn: _to = address(0), pois os tokens deixam de existir.
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
// Emitido quando um dono autoriza um gastador a usar seus tokens.
// Aplicações externas (como DEXes) escutam esse evento para
// saber que podem chamar transferFrom em nome do dono.
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
// =========================================================
// CONSTRUTOR — executado uma única vez no deploy
// =========================================================
/**
* @dev Define o supply inicial e entrega tudo para quem fez o deploy.
* Multiplicamos por 10^18 para respeitar as casas decimais:
* se _initialSupply = 1000, o contrato cria 1000 tokens "humanos",
* que internamente são representados como 1000 * 10^18.
*
* @param _initialSupply Quantidade de tokens a criar (sem decimais)
*/
constructor(uint256 _initialSupply) {
totalSupply = _initialSupply * 10 ** uint256(decimals);
_balances[msg.sender] = totalSupply;
// Convenção: mint emite Transfer com from = address(0)
emit Transfer(address(0), msg.sender, totalSupply);
}
// =========================================================
// CONSULTAS (view) — não custam gas quando chamadas externamente
// =========================================================
/**
* @dev Retorna o saldo de um endereço.
* O valor retornado inclui os decimais — para exibir
* ao usuário, divida por 10^18.
*/
function balanceOf(address _owner) public view returns (uint256) {
return _balances[_owner];
}
/**
* @dev Retorna quanto _spender ainda pode gastar em nome de _owner.
* Diminui a cada transferFrom e pode ser redefinido com approve().
*/
function allowance(
address _owner,
address _spender
) public view returns (uint256) {
return _allowances[_owner][_spender];
}
// =========================================================
// TRANSFERÊNCIA DIRETA
// =========================================================
/**
* @dev Transfere tokens do remetente para _to.
* Só quem chama a função pode mover seus próprios tokens.
*
* @param _to Endereço de destino
* @param _value Quantidade a transferir (com decimais)
*/
function transfer(
address _to,
uint256 _value
) public returns (bool) {
require(_to != address(0), "Destino invalido");
require(_balances[msg.sender] >= _value, "Saldo insuficiente");
_balances[msg.sender] -= _value;
_balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
// =========================================================
// APROVAÇÕES — base do DeFi
// =========================================================
/**
* @dev Autoriza _spender a gastar até _value tokens em seu nome.
*
* Caso de uso típico: você quer usar uma DEX (exchange descentralizada).
* Antes de trocar, você aprova o contrato da DEX para movimentar
* seus tokens — assim ela pode executar a troca sem você
* precisar assinar cada transação individualmente.
*
* ATENÇÃO: chamar approve() sobrescreve a allowance anterior.
* Para reduzir uma allowance existente com segurança, o ideal
* é primeiro zerar (approve 0) e depois setar o novo valor.
*
* @param _spender Endereço autorizado a gastar
* @param _value Limite máximo de gasto
*/
function approve(
address _spender,
uint256 _value
) public returns (bool) {
require(_spender != address(0), "Spender invalido");
_allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
// =========================================================
// TRANSFERÊNCIA DELEGADA
// =========================================================
/**
* @dev Transfere tokens em nome de _from, usando a allowance.
* Quem chama essa função é o _spender aprovado — não o dono.
*
* Fluxo completo:
* 1. Dono chama approve(spender, valor)
* 2. Spender chama transferFrom(dono, destino, valor)
* 3. A allowance diminui automaticamente
*
* @param _from Endereço de origem (quem tem os tokens)
* @param _to Endereço de destino
* @param _value Quantidade a transferir
*/
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool) {
require(_to != address(0), "Destino invalido");
require(_balances[_from] >= _value, "Saldo insuficiente");
require(_allowances[_from][msg.sender] >= _value, "Allowance insuficiente");
_balances[_from] -= _value;
_balances[_to] += _value;
_allowances[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
// =========================================================
// MINT — criar novos tokens
// =========================================================
/**
* @dev Cria novos tokens e entrega para _to.
* Aumenta o totalSupply.
*
* ATENÇÃO: nesse exemplo didático qualquer um pode mintar.
* Em produção, use um modificador onlyOwner ou similar
* para restringir quem pode criar tokens.
*
* @param _to Quem vai receber os tokens criados
* @param _value Quantidade a criar (com decimais)
*/
function mint(address _to, uint256 _value) public {
require(_to != address(0), "Destino invalido");
totalSupply += _value;
_balances[_to] += _value;
// Convenção: mint emite Transfer com from = address(0)
emit Transfer(address(0), _to, _value);
}
// =========================================================
// BURN — destruir tokens
// =========================================================
/**
* @dev Destrói tokens do próprio remetente.
* Diminui o totalSupply — útil para mecanismos deflacionários.
*
* Exemplos de uso real:
* - Projetos que queimam tokens para valorizar os restantes
* - Pagamento de taxas que são destruídas (EIP-1559 no ETH)
*
* @param _value Quantidade a destruir (com decimais)
*/
function burn(uint256 _value) public {
require(_balances[msg.sender] >= _value, "Saldo insuficiente");
_balances[msg.sender] -= _value;
totalSupply -= _value;
// Convenção: burn emite Transfer com to = address(0)
emit Transfer(msg.sender, address(0), _value);
}
}
Como Testar no Remix
- Deploy com
_initialSupply= 1000 - Chame
balanceOf(sua_conta)→ 1000000000000000000000 (1000 * 10^18) - Chame
transfer(outro_endereco, 100000000000000000000)→ transfere 100 tokens - Outro endereço chama
balanceOf()→ confirma recebimento - Outro endereço chama
approve(sua_conta, 50000000000000000000)(50 tokens) - Sua conta chama
transferFrom(outro_endereco, sua_conta, 30000000000000000000)(30 tokens)
Use Case
Criar seu próprio token (tipo seu próprio "Bitcoin") • Utility tokens • Governance tokens