Explorando los Estándares de Tokens en Ethereum: ERC-20, ERC-721 y ERC-1155
Este tutorial profundiza en los estándares de tokens más relevantes de Ethereum: ERC-20 para tokens fungibles, ERC-721 para NFTs únicos y ERC-1155 para tokens multi-fungibles. Comprenderás sus especificaciones, casos de uso y aprenderás a implementar e interactuar con cada uno en Solidity. ¡Prepárate para construir aplicaciones descentralizadas (dApps) más robustas!
🚀 Introducción a los Estándares de Tokens en Ethereum
Ethereum no solo es una plataforma para contratos inteligentes, sino también un ecosistema vibrante para la creación de tokens. Estos tokens representan una vasta gama de activos digitales, desde criptomonedas hasta obras de arte únicas, derechos de voto o incluso objetos en juegos. Para que estos tokens sean interoperables y funcionen de manera predecible en todo el ecosistema de Ethereum, se han establecido estándares.
Los estándares de tokens son conjuntos de reglas y funciones que un contrato inteligente debe implementar para ser reconocido como un tipo de token específico. Estos estándares son cruciales porque permiten que monederos, exchanges, y otras dApps interactúen con cualquier token que siga el estándar de una manera consistente y segura.
En este tutorial, exploraremos los tres estándares de tokens más influyentes y utilizados en Ethereum:
- ERC-20: El rey de los tokens fungibles.
- ERC-721: El pionero de los Non-Fungible Tokens (NFTs).
- ERC-1155: El estándar multi-token, una fusión poderosa de fungibilidad y no fungibilidad.
Comprender estos estándares es fundamental para cualquier desarrollador que aspire a construir aplicaciones descentralizadas (dApps) robustas y funcionales en la blockchain de Ethereum.
📖 ERC-20: La Columna Vertebral de los Tokens Fungibles
El estándar ERC-20 es, sin duda, el más conocido y utilizado. Se estableció en 2015 y desde entonces ha sido la base para la creación de miles de tokens fungibles, como Stablecoins (USDT, USDC), tokens de utilidad (LINK, UNI) y muchos más.
¿Qué es un Token Fungible? 🤔
Un token fungible es aquel en el que cada unidad es idéntica e intercambiable con cualquier otra unidad del mismo token. Piensa en billetes de 10 euros: un billete de 10 euros es igual a cualquier otro billete de 10 euros. No hay diferencia inherente entre ellos. Lo mismo ocurre con el Bitcoin o el Ether; 1 ETH es igual a 1 ETH.
Especificaciones Clave de ERC-20
Un contrato ERC-20 debe implementar un conjunto específico de funciones y eventos. Las funciones obligatorias son:
totalSupply(): Retorna el suministro total de tokens.balanceOf(address _owner): Retorna el balance de tokens de una dirección.transfer(address _to, uint256 _value): Transfiere_valuetokens desde la dirección que llama a_to.transferFrom(address _from, address _to, uint256 _value): Transfiere_valuetokens desde_froma_to, solo si_fromha aprobado previamente a la dirección que llama.approve(address _spender, uint256 _value): Permite que_spenderretire_valuetokens de la cuenta del que llama.allowance(address _owner, address _spender): Retorna la cantidad de tokens que_spenderpuede retirar de_owner.
Además, debe emitir dos eventos:
Transfer(address indexed _from, address indexed _to, uint256 _value)Approval(address indexed _owner, address indexed _spender, uint256 _value)
Caso de Uso: Un Token de Votación Simple con ERC-20
Imaginemos que queremos crear un token para un sistema de votación. Cada token representa un voto. Como todos los votos son iguales, ERC-20 es la elección perfecta.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract VotingToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Voting Token", "VOTE") {
_mint(msg.sender, initialSupply);
}
// Implementación mínima. Un token de votación podría tener más lógica,
// pero la base es un ERC-20 estándar.
}
Este ejemplo utiliza la implementación de OpenZeppelin, que es la forma recomendada de trabajar con estándares de tokens debido a su seguridad y auditorías. El constructor _mint emite la initialSupply de tokens al desplegar el contrato.
Interacción con ERC-20
Para interactuar con un token ERC-20 desde otro contrato, generalmente se define una interfaz.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract TokenInteractor {
function sendTokens(address tokenAddress, address recipient, uint256 amount) public returns (bool) {
IERC20 token = IERC20(tokenAddress);
return token.transfer(recipient, amount);
}
function approveSpending(address tokenAddress, address spender, uint256 amount) public returns (bool) {
IERC20 token = IERC20(tokenAddress);
return token.approve(spender, amount);
}
function getBalance(address tokenAddress, address account) public view returns (uint256) {
IERC20 token = IERC20(tokenAddress);
return token.balanceOf(account);
}
}
Aquí, TokenInteractor puede enviar tokens, aprobar gastos y verificar balances de cualquier token ERC-20, simplemente conociendo la dirección de su contrato.
🎨 ERC-721: La Era de los Non-Fungible Tokens (NFTs)
El estándar ERC-721 revolucionó el espacio de los activos digitales al introducir el concepto de Non-Fungible Tokens (NFTs). A diferencia de los tokens ERC-20, donde cada unidad es idéntica, cada token ERC-721 es único e irremplazable.
¿Qué es un Token No Fungible? 🖼️
Un NFT es como una obra de arte original, una casa, o un coche específico. Cada uno tiene sus propias características y un identificador único. No puedes simplemente cambiar un NFT por otro sin que haya una diferencia percibida o real en su valor o propiedades.
Especificaciones Clave de ERC-721
Los contratos ERC-721 deben implementar funciones para gestionar la propiedad de tokens individuales. Algunas funciones clave incluyen:
balanceOf(address _owner): Retorna el número de NFTs que posee una dirección.ownerOf(uint256 _tokenId): Retorna la dirección del propietario de un NFT específico por su_tokenId.approve(address _to, uint256 _tokenId): Permite a_togestionar un NFT específico (_tokenId).getApproved(uint256 _tokenId): Retorna la dirección que está aprobada para gestionar un_tokenIdespecífico.setApprovalForAll(address _operator, bool _approved): Permite que un_operatorgestione todos los NFTs de un propietario.isApprovedForAll(address _owner, address _operator): Retorna si un_operatortiene permiso para gestionar todos los NFTs de_owner.transferFrom(address _from, address _to, uint256 _tokenId): Transfiere la propiedad de un NFT (_tokenId) de_froma_to. (Hay versionessafeTransferFromque son preferibles para prevenir la pérdida de tokens).
Los eventos obligatorios son:
Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId)Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId)ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)
Caso de Uso: Un Coleccionable Digital Único (NFT Art)
Creamos un NFT que representa una obra de arte digital única.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract DigitalArtNFT is ERC721, Ownable {
uint256 private _tokenIdCounter;
constructor() ERC721("Digital Art", "ART") {
_tokenIdCounter = 0;
}
function mintArt(address recipient, string memory tokenURI) public onlyOwner returns (uint256) {
_tokenIdCounter++;
_safeMint(recipient, _tokenIdCounter);
_setTokenURI(_tokenIdCounter, tokenURI);
return _tokenIdCounter;
}
// La función tokenURI ya está implementada en ERC721 para devolver el URI asociado a un token.
}
Este contrato DigitalArtNFT permite al propietario (quien desplegó el contrato) acuñar nuevas obras de arte (NFTs) y asignarlas a un recipient. Cada obra tendrá un _tokenId único y un tokenURI que apunta a los metadatos y la imagen de la obra.
Interacción con ERC-721
Similar al ERC-20, la interacción se realiza a través de interfaces.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
contract NFTMarketplace {
IERC721 public nftContract;
constructor(address _nftContractAddress) {
nftContract = IERC721(_nftContractAddress);
}
function listNFTForSale(uint256 tokenId, uint256 price) public {
require(nftContract.ownerOf(tokenId) == msg.sender, "You don't own this NFT");
// El vendedor debe aprobar previamente a este contrato para mover el NFT
require(nftContract.getApproved(tokenId) == address(this) || nftContract.isApprovedForAll(msg.sender, address(this)), "Marketplace not approved to transfer NFT");
// Lógica para listar el NFT, almacenar el precio, etc.
// ...
}
function buyNFT(uint256 tokenId) public payable {
// Lógica para verificar el precio, transferir Ether al vendedor,
// y luego transferir el NFT al comprador.
nftContract.safeTransferFrom(nftContract.ownerOf(tokenId), msg.sender, tokenId);
// ...
}
}
Este NFTMarketplace puede interactuar con cualquier contrato ERC-721 para listar y comprar NFTs. Es crucial que el vendedor apruebe al contrato del marketplace antes de que este pueda mover el NFT en su nombre.
✨ ERC-1155: La Versatilidad del Multi-Token Estándar
ERC-1155 es un estándar relativamente más nuevo, propuesto por Enjin, que busca abordar las limitaciones de ERC-20 y ERC-721, ofreciendo un contrato que puede manejar múltiples tipos de tokens a la vez, tanto fungibles como no fungibles. Es extremadamente eficiente para entornos de juegos, metaversos y sistemas que requieren una gran variedad de activos.
¿Qué es un Token Multi-Fungible? 🔄
Imagina un juego donde necesitas monedas (fungibles), espadas únicas (no fungibles) y pociones curativas (fungibles, pero quizás con diferentes propiedades). Con ERC-1155, puedes gestionar todos estos activos dentro de un único contrato. Cada tokenId en un ERC-1155 puede representar una clase diferente de activo, y esa clase puede ser fungible o no fungible.
- Fungible: Si
balanceOf(id)se comporta como un ERC-20 (p.ej.,tokenId = 1representa 'monedas de oro'). - No Fungible: Si
balanceOf(id)solo puede ser 0 o 1 para un propietario (p.ej.,tokenId = 2representa 'Espada Maestra').
Especificaciones Clave de ERC-1155
ERC-1155 optimiza la gestión de lotes de transferencias y aprobaciones, reduciendo significativamente los costes de gas. Las funciones clave son:
balanceOf(address _owner, uint256 _id): Retorna el balance de un token específico (_id) para un_owner.balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids): Retorna los balances de múltiples tokens para múltiples propietarios en una sola llamada.setApprovalForAll(address _operator, bool _approved): Permite que un_operatorgestione todos los tokens de un propietario (similar a ERC-721).isApprovedForAll(address _owner, address _operator): Verifica si un_operatorestá aprobado.safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data): Transfiere_valuetokens de_iddesde_froma_to.safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data): Transfiere múltiples tipos de tokens en un solo lote.
Eventos obligatorios:
TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value)TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] calldata _ids, uint256[] calldata _values)ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)URI(string _value, uint256 indexed _id): Emite cuando se actualiza la URI de metadatos de un_id.
Caso de Uso: Ítems de Juego con ERC-1155
Creemos un contrato para gestionar ítems en un juego: monedas (fungible), espadas legendarias (no fungible) y pociones (fungible).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant LEGENDARY_SWORD = 1;
uint256 public constant HEALING_POTION = 2;
constructor() ERC1155("https://game.example/items/{id}.json") {
// URIs de metadatos base. El {id} será reemplazado por el ID del token.
// Puedes tener diferentes URIs para diferentes IDs o un URI base para todos.
}
function mint(address account, uint256 id, uint256 amount, bytes memory data) public onlyOwner {
_mint(account, id, amount, data);
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public onlyOwner {
_mintBatch(to, ids, amounts, data);
}
// Para hacer un token no fungible, simplemente se acuña solo 1 unidad y se gestiona como tal.
// Por ejemplo, para LEGENDARY_SWORD, solo acuñarías una cantidad de 1 para un id específico.
// Sobreescribir _beforeTokenTransfer para añadir lógica específica si es necesario.
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
}
}
En este contrato GameItems, GOLD y HEALING_POTION serían fungibles (puedes acuñar y transferir varias unidades). LEGENDARY_SWORD sería un NFT si solo se acuña una unidad para ese tokenId o si se controla su totalSupply para ser 1. La belleza de ERC-1155 es que el mismo contrato maneja ambos paradigmas.
Interacción con ERC-1155
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC1155 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}
contract ItemShop {
IERC1155 public gameItems;
constructor(address _gameItemsAddress) {
gameItems = IERC1155(_gameItemsAddress);
}
function buyGold(uint256 amount) public payable {
require(msg.value >= amount * 1 ether / 100, "Not enough ETH for gold"); // Ejemplo: 100 oro = 1 ETH
// Asumimos que ItemShop tiene permiso para acuñar o recibir gold de alguna fuente
// En un caso real, el contrato GameItems tendría una función para permitir la compra.
gameItems.safeTransferFrom(address(this), msg.sender, gameItems.GOLD(), amount, "");
}
function sellItem(uint256 id, uint256 amount) public {
// El vendedor debe aprobar previamente a ItemShop para transferir los ítems.
require(gameItems.isApprovedForAll(msg.sender, address(this)), "Shop not approved to transfer items");
gameItems.safeTransferFrom(msg.sender, address(this), id, amount, "");
// Lógica para enviar ETH o tokens al vendedor
}
}
ItemShop puede interactuar con el contrato GameItems para que los usuarios compren oro o vendan otros ítems. Las funciones safeTransferFrom y safeBatchTransferFrom de ERC-1155 son muy eficientes para mover múltiples ítems a la vez.
⚖️ Comparativa de Estándares: ERC-20 vs. ERC-721 vs. ERC-1155
Aquí tienes una tabla resumen de las principales diferencias y casos de uso:
| Característica | ERC-20 | ERC-721 | ERC-1155 |
|---|---|---|---|
| --- | --- | --- | --- |
| Fungibilidad | Fungible (todas las unidades iguales) | No fungible (cada unidad es única) | Multi-fungible (puede ser ambos) |
| ID de Token | No aplica (todas las unidades son el mismo token) | uint256 ID único por cada token | uint256 ID único por tipo de token |
| --- | --- | --- | --- |
| Balance | Balance total de tokens por dirección | Número de NFTs poseídos por dirección | Balance de cada id de token por dirección |
| Transferencias | transfer, transferFrom | transferFrom, safeTransferFrom | safeTransferFrom, safeBatchTransferFrom |
| --- | --- | --- | --- |
| Aprobaciones | approve para cantidad de tokens | approve para un tokenId, setApprovalForAll | setApprovalForAll (para todos los IDs) |
| Casos de Uso Típicos | Criptomonedas, stablecoins, tokens de utilidad, gobernanza | Arte digital, coleccionables, tickets, bienes raíces digitales | Ítems de juego, membresías, licencias, sistemas de inventario complejos |
| --- | --- | --- | --- |
| Eficiencia de Gas | Buena para transferencias individuales | Más alto para transferencias masivas de NFTs individuales | Muy eficiente, especialmente para transferencias por lotes |
🛠️ Herramientas y Buenas Prácticas
OpenZeppelin Contracts
La forma más segura y recomendada de implementar estos estándares es utilizando las bibliotecas de contratos inteligentes de OpenZeppelin Contracts. Estos contratos están ampliamente auditados y siguen las mejores prácticas de seguridad, ahorrándote tiempo y reduciendo drásticamente el riesgo de vulnerabilidades.
Desarrollo y Pruebas
- Hardhat o Foundry: Utiliza frameworks de desarrollo como Hardhat o Foundry para desplegar y probar tus contratos localmente.
- Remix IDE: Para prototipos rápidos y aprendizaje, Remix IDE es una excelente herramienta en el navegador.
- Ethers.js / Web3.js: Para interactuar con tus contratos desde el frontend de una dApp.
Consideraciones de Seguridad
- Reentrancy: Asegúrate de que tus contratos no sean vulnerables a ataques de reentrancy, especialmente al interactuar con otros contratos o enviar Ether.
- Overflow/Underflow: Aunque Solidity 0.8.0+ lo maneja por defecto, si usas versiones anteriores, sé consciente de los desbordamientos/subdesbordamientos aritméticos.
- Aprobaciones y Gastos: Ten cuidado con la lógica que maneja las aprobaciones y el gasto de tokens por parte de terceros. Limita los permisos al mínimo necesario.
🎯 Conclusión
Los estándares de tokens ERC-20, ERC-721 y ERC-1155 son la base sobre la que se construyen gran parte de las aplicaciones descentralizadas en Ethereum. Cada uno tiene un propósito específico y optimizado para diferentes tipos de activos digitales:
- ERC-20 para activos fungibles, el estándar de facto para criptomonedas y tokens de utilidad.
- ERC-721 para activos únicos y no fungibles, impulsando la explosión de los NFTs.
- ERC-1155 para una gestión eficiente y versátil de múltiples tipos de activos, ideal para juegos y metaversos.
Al dominar estos estándares, estás bien equipado para diseñar y construir una amplia gama de dApps, desde sistemas financieros hasta complejos ecosistemas de juegos. La elección del estándar correcto es un paso crítico en el diseño de cualquier contrato inteligente que involucre tokens.
¡Ahora tienes el conocimiento para elegir el estándar de token adecuado para tu próximo proyecto en Ethereum y Solidity! ¡Feliz desarrollo! 🚀
Tutoriales relacionados
- Decentralized Autonomous Organizations (DAOs) en Ethereum: Creación y Gestión con Solidityintermediate25 min
- Optimización de Gas en Solidity: Estrategias Avanzadas para Contratos Inteligentes Eficientesintermediate12 min
- Desarrollando Contratos Inteligentes Auto-Actualizables en Solidity: Patrones de Actualización en Ethereumadvanced20 min
- Navegando el Laberinto del Almacenamiento en Solidity: Entendiendo Storage, Memory y Calldata para Contratos Eficientesintermediate18 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!