Introducción
Los NFTs se crearon originalmente para representar la propiedad de activos digitales o físicos, como coleccionables. Sin embargo, estaban limitados a rastrear la propiedad de un ID y sus metadatos asociados; no podían poseer otros NFTs o tokens ERC-20, ni interactuar con protocolos DeFi como las cuentas regulares de Ethereum. ERC-6551 cambia esto al darle a cada NFT su propia cuenta de contrato inteligente, conocida como Token Bound Account (TBA). Las TBAs pueden:
- Poseer activos como tokens ERC-20, ETH, ERC-721, ERC-1155 y cualquier otro token que acepte tu billetera
- Ejecutar transacciones on-chain cuando son iniciadas por el propietario del NFT (EOA)
- Mantener un historial de transacciones como cualquier otra cuenta de Ethereum
Por ejemplo, en un juego blockchain, el acceso al juego puede requerir que compres un NFT de personaje. Con ERC-6551, el NFT del personaje obtiene su propia TBA (una cuenta de contrato inteligente). A medida que progresas en el juego, la TBA de tu personaje almacena los activos que acumulas.
Si decides vender este NFT de personaje, el comprador no solo adquiere el NFT, sino también su TBA, que contiene todos tus activos acumulados, a menos que los transfieras antes. Es como comprar un personaje totalmente equipado y con experiencia en lugar de empezar desde cero.
Bajo el estándar ERC-6551:
- A cada NFT se le asigna una o más direcciones deterministas de TBA, calculadas por el contrato de registro.
- La TBA funciona como una cuenta de contrato inteligente controlada por el propietario del NFT.
- Todas las acciones se ejecutan a través de la TBA.
- El historial de transacciones y los activos están vinculados a la TBA.
Estas cuentas de contratos inteligentes se crean para los NFTs y no requieren cambios en los contratos de NFT existentes. Debido a que están desacopladas del NFT en sí, son compatibles hacia el futuro y retrocompatibles con los estándares de NFT actuales y futuros.
En este artículo, aprenderás el flujo y la mecánica central del estándar ERC-6551.
Requisitos previos
Este artículo asume que el lector está familiarizado con lo siguiente:
- Estándar ERC-721
- Estándar minimal proxy EIP-1167
- EIP-1014 (
CREATE2) - Método de validación de firmas ERC-1271 para contratos
Características Clave del Estándar ERC-6551
El patrón ERC-6551 vincula implícitamente los NFTs a direcciones de contratos inteligentes utilizando el mecanismo de dirección determinista de CREATE2.
Específicamente, los NFTs se identifican de manera única mediante la tupla (chainId, tokenContract, tokenId). La desventaja de usar solo estos parámetros es que cada NFT estaría limitado a una sola cuenta.
En algunos casos, un NFT puede requerir múltiples TBAs, al igual que los usuarios regulares de Ethereum que tienen billeteras separadas para diferentes propósitos (almacenamiento en caliente/frío). Aquí es donde entra en juego un parámetro salt.
Cada valor salt único ayuda a generar una TBA diferente para el mismo NFT, lo que permite que un solo NFT administre múltiples TBAs. Esto extiende la tupla a: (chainId, tokenContract, tokenId, salt).
Recuerda que la dirección que despliega CREATE2 se calcula de la siguiente manera:
predictedAddress = address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
deployer,
salt,
keccak256(_initCode)
)))));
Dado que la dirección del deployer afecta el cálculo de la dirección CREATE2, todas las TBAs deben provenir de un deployer común (factory). De lo contrario, sería necesario rastrear qué factory desplegó qué TBA.
Sin embargo, este requisito crea un desafío: para un direccionamiento predecible, las TBAs desplegadas desde el mismo factory necesitan tener un bytecode de inicialización idéntico (_initCode). Integrar diferentes lógicas personalizadas en cada TBA alteraría el bytecode de inicialización y rompería la predictibilidad de la dirección.
Para resolver esto, cada TBA se despliega como un clon proxy mínimo EIP-1167 personalizado que extiende su bytecode para incluir datos inmutables adicionales (chainId, tokenContract, tokenId y salt) para la vinculación de la cuenta, mientras delega la ejecución a un contrato de implementación separado. Esto mantiene el código de inicialización constante para la predictibilidad de la dirección, al tiempo que permite que las TBAs del mismo factory utilicen diferentes implementaciones.
Estructura del Bytecode de una TBA
Cada TBA desplegada desde el factory tiene la siguiente estructura de bytecode:
[EIP-1167 Minimal Proxy]
├── Header (10 bytes) - Inicialización estándar del proxy
├── Implementation (20 bytes) - Dirección del contrato objetivo
└── Footer (15 bytes) - Lógica de delegación
[Immutable Account Data] (datos que vinculan la TBA a un NFT específico)
├── Salt (32 bytes) - Para derivación de la dirección create2
├── ChainID (32 bytes) - identificador de la cadena donde existe el NFT
├── TokenContract (32 bytes) - Dirección del contrato del NFT
└── TokenID (32 bytes) - Identificador del NFT
Por ejemplo, la estructura del bytecode se verá de la siguiente manera:
// ERC-1167 Proxy Section
363d3d373d3d3d363d73 // Header - copy calldata
bebebebebebebebebebebebebebebebebebebebe //Implementation
5af43d82803e903d91602b57fd5bf3 // Footer - delegate call section
// Immutable Data Section
0000...0000 // Salt (32 bytes of zeros)
0000...0001 // ChainID (1 for Ethereum mainnet)
cfcf...cfcf // NFT contract address
0000...007b // TokenID (123 in hex)
Cuando el propietario de un NFT interactúa con su TBA, la TBA (clon proxy) mantiene su propio estado (como NFTs acumulados o activos del juego) mientras delega la lógica al contrato de implementación que utiliza.
Dada esta arquitectura proxy, se requiere un parámetro adicional: la dirección de implementation. En consecuencia, la tupla completa para identificar la dirección de la TBA pasa a ser (implementation, salt, chainId, tokenContract, tokenId).
Este método de identificación, combinado con CREATE2, permite a cualquiera calcular la dirección de una TBA a pedido sin almacenar asignaciones (mappings). Como resultado, elimina los costos de almacenamiento al mismo tiempo que garantiza que cada TBA tenga una dirección única y predecible.
Contrato de Registro
El registro canónico sirve como el factory que despliega estas TBAs. Dado que las TBAs delegan la ejecución, su implementación debe ser desplegada primero.
Para crear una TBA, la función createAccount() del registro toma todos los parámetros de la tupla (implementation, salt, chainId, tokenContract, tokenId) y despliega un clon proxy.

El código en assembly resaltado a continuación en la función createAccount() del registro es lo que crea el bytecode de proxy especializado combinando el patrón EIP-1167 con los datos de vinculación del NFT en la memoria:

Con este diseño, cualquier contrato puede verificar fácilmente a qué NFT está vinculada una cuenta leyendo los datos de su bytecode. Esto se explicará con más detalle durante el tutorial de creación de una TBA más adelante en el artículo.
Funciones del Contrato de Registro
El registro debe implementar IERC6551Registry que define dos funciones principales:
createAccount()
Crea una token bound account (TBA) para un NFT específico.

Si ya existe una cuenta, createAccount() simplemente devuelve la dirección de la cuenta calculada sin llamar a CREATE2. Esto lo hace verificando si hay código existente en la dirección calculada utilizando iszero(extcodesize(computed)) como se resalta con la flecha roja a continuación; si no hay código (tamaño = 0), significa que aún no existe una cuenta, por lo que el registro crea una nueva TBA en esa dirección exacta, y luego createAccount() devuelve la dirección de la TBA recién creada en la línea 121. Sin embargo, si el código ya existe (tamaño > 0), devuelve la dirección de la cuenta existente sin desplegar una nueva.

Aunque se puede usar cualquier valor salt, la mayoría de las TBAs se despliegan con un salt predeterminado de bytes32(0). Como se mencionó en la sección “Características Clave del Estándar ERC-6551”, usar diferentes valores de salt permite que un NFT posea múltiples TBAs para diferentes propósitos."
account()
Calcula y devuelve la dirección determinista de una TBA para un NFT dado sin crearla.

Evento AccountCreated()
Se requiere que el registro emita un evento, ERC6551AccountCreated, el cual se emite solo cuando createAccount() crea con éxito una nueva TBA:

Ayuda a las dApps y a los indexadores a rastrear qué implementación utiliza cada TBA, y a monitorizar las creaciones de TBAs.
Despliegue del Contrato de Registro
El contrato de registro ERC-6551 se despliega en una dirección fija (0x000000006551c19487814612e58FE06813775758) en todas las cadenas compatibles con EVM.
Cabe destacar que la parte “6551” de la dirección fue elegida intencionalmente como un patrón personalizado (vanity pattern) iterando a través de diferentes salts hasta obtener la dirección deseada.
Se puede encontrar una lista de las direcciones del registro desplegadas en varias cadenas aquí.
Mecanismo de Despliegue del Registro Cross-chain
Para garantizar que el contrato de registro se despliegue en la misma dirección en todas las cadenas, el registro utiliza el factory de Nick: el factory de Nick es un contrato factory de CREATE2 desplegado en la misma dirección (0x4e59b44847b379578588920cA78FbF26c0B4956C) en todas las cadenas. Esto hace posible el despliegue determinista de contratos a través de diferentes cadenas.
El factory de Nick utiliza CREATE2 para desplegar otros contratos en direcciones predecibles donde la dirección desplegada se calcula a partir de: la dirección del factory, un valor salt y el bytecode del contrato (en este caso, el registro).
Por lo tanto, para que el contrato de registro se despliegue en una dirección coherente en todas las cadenas, el factory de Nick debe existir en la dirección 0x4e59b44847b379578588920cA78FbF26c0B4956C en todas las cadenas.
Para desplegar el registro en una cadena donde aún no ha sido desplegado, envía la siguiente transacción de despliegue:
{
"to": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
"value": "0x0",
"data": "0x0000000000000000000000000000000000000000fd8eb4e1dca713016c518e31608060405234801561001057600080fd5b5061023b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063246a00211461003b5780638a54c52f1461006a575b600080fd5b61004e6100493660046101b7565b61007d565b6040516001600160a01b03909116815260200160405180910390f35b61004e6100783660046101b7565b6100e1565b600060806024608c376e5af43d82803e903d91602b57fd5bf3606c5285605d52733d60ad80600a3d3981f3363d3d373d3d3d363d7360495260ff60005360b76055206035523060601b60015284601552605560002060601b60601c60005260206000f35b600060806024608c376e5af43d82803e903d91602b57fd5bf3606c5285605d52733d60ad80600a3d3981f3363d3d373d3d3d363d7360495260ff60005360b76055206035523060601b600152846015526055600020803b61018b578560b760556000f580610157576320188a596000526004601cfd5b80606c52508284887f79f19b3655ee38b1ce526556b7731a20c8f218fbda4a3990b6cc4172fdf887226060606ca46020606cf35b8060601b60601c60005260206000f35b80356001600160a01b03811681146101b257600080fd5b919050565b600080600080600060a086880312156101cf57600080fd5b6101d88661019b565b945060208601359350604086013592506101f46060870161019b565b94979396509194608001359291505056fea2646970667358221220ea2fe53af507453c64dd7c1db05549fa47a298dfb825d6d11e1689856135f16764736f6c63430008110033",
}
Entendiendo la transacción de despliegue del registro:
to:0x4e59b44847b379578588920ca78fbf26c0b4956c(dirección del factory de Nick)value:0x0(No se envía ETH con el despliegue)data: Consiste en los primeros 32 bytes que representan el parámetro salt:0x0000000000000000000000000000000000000000fd8eb4e1dca713016c518e31, y los bytes restantes que son el bytecode del contrato de registro.
Esta transacción de despliegue envía datos al factory de Nick, que luego extrae el salt y el bytecode del contrato para desplegar el registro en la dirección predefinida. Este enfoque garantiza un registro único y canónico que cualquiera puede desplegar.
Con el registro manejando la creación de las cuentas vinculadas a tokens, el siguiente enfoque está en la implementación de las TBAs.
Cada TBA debe seguir interfaces específicas que definen las funciones centrales requeridas para la compatibilidad con el estándar ERC-6551. Estas interfaces garantizan una integración adecuada con billeteras, marketplaces y aplicaciones.
Interfaces de la TBA
Para cumplir con el estándar ERC-6551, cada implementación de TBA debe implementar IERC6551Account para la identificación y autorización de la cuenta, IERC6551Executable para la ejecución de operaciones, IERC165 para la detección de la interfaz e IERC1271 para la validación de firmas.
Las funciones de cada interfaz—
IERC6551Account,IERC6551ExecutableeIERC165junto conIERC1271, se explican individualmente. Su integración se abordará en la subsección “Cómo Funcionan Juntas Estas Interfaces”.
Interfaz de Cuenta (IERC6551Account)
Cuando una TBA interactúa con otros contratos o protocolos, esos contratos y protocolos necesitan una forma estandarizada de verificar información importante como: a qué NFT está vinculada esta TBA, si el firmante de la transacción está autorizado y cuál es el estado de ejecución actual (nonce) de la TBA.
La interfaz IERC6551Account define un conjunto de funciones centrales para abordar las necesidades de verificación antes mencionadas:

Las siguientes tres son relevantes para nuestra discusión sobre las interfaces de las TBAs:
token()- Lee los datos integrados del bytecode de la cuenta que realiza la llamada y devuelve una tupla que contiene el NFT (chainId, contract address, tokenId) que está vinculado a la cuenta.isValidSigner()- Valida si una dirección está autorizada para firmar en nombre de la cuenta.state()- Devuelve un valor uint256 que rastrea el nonce de operación de la cuenta.
El propósito de la interfaz es definir las funciones básicas que toda implementación de TBA debe tener, dejando espacio para que las implementaciones agreguen funcionalidad ampliada.
Toda implementación de TBA debe también soportar:
Detección de Interfaz ERC-165

Para permitir que otros contratos verifiquen en tiempo de ejecución que implementan las interfaces IERC6551Account e IERC6551Executable requeridas antes de interactuar con ellas.
Interfaz de Validación de Firmas ERC-1271

La implementación de referencia de la página oficial del EIP-6551 utiliza ERC-1271 para abordar cómo los protocolos pueden verificar que una TBA ha autorizado una transacción. Implementa la función isValidSignature() que comprueba si la firma es válida para el propietario del NFT, devolviendo 0x1626ba7e si es válida o bytes4(0) si es inválida.
0x1626ba7e se deriva del selector de función de isValidSignature(bytes32,bytes), calculado como:
keccak256("isValidSignature(bytes32,bytes)")
Luego, los primeros 4 bytes (bytes4) del hash se toman como el valor de retorno.

Interfaz de Ejecución (IERC6551Executable)

IERC6551Executable consta de una única función requerida execute() que permite a las implementaciones de TBA realizar operaciones de bajo nivel cuando son llamadas por un firmante válido.
La interfaz no tiene eventos obligatorios que deban emitirse.
A través de execute(), las implementaciones de TBA definen qué operaciones soportan. El parámetro operation es un valor uint8 que señala qué acción de bajo nivel debe realizarse:
0 = CALL // Regular calls (sending ETH, interacting with contracts)
1 = DELEGATECALL // Execute code from another contract in TBA's context
2 = CREATE // Deploy new contracts
3 = CREATE2 // Deploy contracts with deterministic addresses
Considera un escenario en el que el propietario de un NFT quiere que su TBA deposite tokens ERC-20 en un protocolo. Esto requiere que el propietario del NFT llame a execute() en la TBA para cada operación, que luego delega al contrato de implementación para realizar las transacciones.
Aprobando tokens:
execute(
to: tokenContract, // ERC20 token address
value: 0, // No ETH sent
data: abi.encodeWithSignature(
"approve(address,uint256)",
spender, // Protocol address to approve
amount // Amount to approve
),
operation: 0 // CALL operation
);
Depositando en el protocolo:
execute(
to: protocol address,
value: 0, // no ETH sent
data: depositData,// encoded deposit() call
operation: 0 // operation: CALL
);
O para simplemente enviar ETH:
execute(
to: recipient,
value: 2 ether,
data: "", // empty for ETH transfer
operation: 0 // operation: CALL
);
Cada operación debe ser llamada por un firmante válido y debe incluir un manejo de errores adecuado.
La interfaz IERC6551Executable es flexible en el sentido de que en lugar de imponer una forma específica de manejar las transacciones, solo requiere que las implementaciones de TBA señalen claramente (a través de ERC-165) qué interfaz de ejecución soportan, ya sea la interfaz estándar IERC6551Executable o su propio mecanismo de ejecución personalizado.
Cómo Funcionan Juntas Estas Interfaces
La implementación de referencia destaca cómo IERC6551Account, IERC6551Executable, IERC165 y ERC-1271 trabajan juntas para permitir que las TBAs ejecuten transacciones y verifiquen que un mensaje fue firmado por su propietario del NFT (EOA). Estas interfaces conforman el núcleo de cómo funciona una TBA, como se muestra en la implementación de referencia:

Cuando una TBA necesita ejecutar una transacción, estas funciones de verificación entran en juego:
isValidSignature() (de ERC-1271) valida que una firma es válida para el propietario del NFT vinculado

- Comprueba si el
owner()(EOA) de la TBA creó la firma. La implementación de referencia utiliza SignatureChecker de OpenZeppelin para esta verificación.
isValidSigner() (de IERC6551Account) comprueba si una dirección dada (caller) está autorizada para ejecutar transacciones en nombre de la TBA

- Verifica la dirección frente al propietario del NFT. Esto se utiliza principalmente en la función
execute()(de IERC6551Executable) para validar al que llama (caller).

Ten en cuenta que la implementación de referencia utiliza una función interna _isValidSigner() que solo comprueba que la dirección dada es la del propietario del NFT, pero se puede incluir lógica de verificación adicional para autorizar a otras direcciones además del propietario.
Para los propietarios de NFTs que quieran interactuar con sus TBAs, o para los intercambios descentralizados (DEX) y marketplaces que se integran con las TBAs, el flujo de trabajo generalmente es así:
-
Transacción Directa:
- El usuario (propietario del NFT) envía una transacción a su TBA llamando a
execute() - La función
execute()verifica que el llamador esté autorizado a través de_isValidSigner(msg.sender) - Si está autorizado, la TBA realiza la operación solicitada, por ejemplo, transferir tokens ERC-20
- Se incrementa
stateen el almacenamiento después de cada ejecución, previniendo ataques de repetición (replay attacks).
- El usuario (propietario del NFT) envía una transacción a su TBA llamando a
-
Basada en Firmas:
Ten en cuenta que esto requiere que los protocolos implementen soporte para ERC-1271, el cual no fue adoptado ampliamente cuando se diseñaron muchos DEXs y puede que no soporten el método de verificación de firmas de contratos de forma nativa.-
El usuario firma un mensaje con su billetera (que posee el NFT) que contiene los detalles de la transacción (p. ej., “Intercambiar 100 USDC por 0.05 ETH desde mi TBA”)
-
El DEX llama a
isValidSignature()en la TBA, usando el hash del mensaje y la firma del propietario del NFT como argumentos, para verificar ambos:- La validez de la firma
- Y que la firma fue producida por el propietario actual del NFT vinculado a la TBA, confirmando que tienen autoridad sobre la cuenta de la TBA
-
Si se verifica, el DEX sabe que la transacción está autorizada, pero para ejecutar realmente el intercambio, la TBA debe otorgar aprobaciones de tokens al DEX a través de la llamada
execute()
-
execute(
to: tokenContract, // USDC token address
value: 0, // No ETH sent
data: abi.encodeWithSignature(
"approve(address,uint256)",
spender, // Protocol address to approve
100 // Amount to approve
),
operation: 0 // CALL operation
);
Las interfaces IERC6551Account, IERC6551Executable y ERC-1271 proporcionan colectivamente una forma estandarizada para que las TBAs verifiquen firmas y ejecuten transacciones autorizadas, haciéndolas compatibles tanto con llamadas a funciones directas como con protocolos que utilizan la verificación de firmas ERC-1271.
Cómo Crear una TBA para un NFT
Para crear una TBA, sigue estos sencillos pasos:
- Crea y despliega un contrato de implementación.
- Interactúa con el registro en
0x000000006551c19487814612e58FE06813775758llamando acreateAccount()y pasando los parámetros necesarios:(implementation, salt, chainId, tokenContract, tokenId).- Si ya tienes un contrato de implementación adecuado desplegado, puedes pasar su dirección como argumento
implementationdirectamente sin crear uno nuevo.
- Si ya tienes un contrato de implementación adecuado desplegado, puedes pasar su dirección como argumento
Para este tutorial, la implementación de referencia se ha extendido con una función de permisos que permitirá que el propietario de un NFT conceda acceso a otras direcciones para ejecutar transacciones en nombre de su TBA.
El contrato de implementación de referencia personalizado:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
interface IERC6551Account {
receive() external payable;
function token() external view returns (uint256 chainId, address tokenContract, uint256 tokenId);
function state() external view returns (uint256);
function isValidSigner(address signer, bytes calldata context) external view returns (bytes4 magicValue);
}
interface IERC6551Executable {
function execute(address to, uint256 value, bytes calldata data, uint8 operation)
external payable returns (bytes memory);
}
contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executable {
uint256 public state;
/// @notice Mapping to store addresses permitted to act on behalf of the account
mapping(address => bool) public isPermitted;
receive() external payable {}
function execute(address to, uint256 value, bytes calldata data, uint8 operation)
external payable virtual returns (bytes memory result) {
require(_isValidSigner(msg.sender), "Invalid signer");
require(operation == 0, "Only call operations are supported");
++state;
bool success;
(success, result) = to.call{value: value}(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
/// @notice Allows the NFT owner to grant or revoke permission for another address
function setPermission(address user, bool permitted) external {
require(msg.sender == owner(), "Only owner can set permissions");
require(user != msg.sender, "Cannot set permission for yourself");
isPermitted[user] = permitted;
}
function isValidSigner(address signer, bytes calldata) external view virtual returns (bytes4) {
return _isValidSigner(signer) ? IERC6551Account.isValidSigner.selector : bytes4(0);
}
function isValidSignature(bytes32 hash, bytes memory signature)
external view virtual returns (bytes4 magicValue) {
return SignatureChecker.isValidSignatureNow(owner(), hash, signature)
? IERC1271.isValidSignature.selector
: bytes4(0);
}
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId
|| interfaceId == type(IERC6551Account).interfaceId
|| interfaceId == type(IERC6551Executable).interfaceId;
}
function token() public view virtual returns (uint256, address, uint256) {
bytes memory footer = new bytes(0x60);
assembly {
extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
}
return abi.decode(footer, (uint256, address, uint256));
}
function owner() public view virtual returns (address) {
(uint256 chainId, address tokenContract, uint256 tokenId) = token();
return (chainId == block.chainid) ? IERC721(tokenContract).ownerOf(tokenId) : address(0);
}
function _isValidSigner(address signer) internal view virtual returns (bool) {
return signer == owner() || isPermitted[signer];
}
}
Las siguientes son las nuevas adiciones al contrato de implementación de referencia:
mapping(address => bool) isPermitted: Rastrea qué direcciones tienen permisos para actuar en nombre de la cuentasetPermission(address user, bool permitted): Función externa que permite al propietario del NFT conceder o revocar permisos- Una función
_isValidSigner()actualizada que ahora comprueba si el firmante es el propietario del NFT o una dirección autorizada.
Para crear una TBA para un NFT sigue estos pasos:
- Clona este repositorio:
git clone https://github.com/Sayrarh/ERC-6551-Reference-Implementation.git - Instala las dependencias:
npm install - Crea tu archivo
.envcopiando el contenido de.env.exampley completando tus claves de API - Ve al archivo
scripts/interaction.tsy reemplazanftContractAddresscon tu dirección de NFT real, junto con los parámetrosnfttokenIdychainId

Luego ejecuta el script: npx hardhat run scripts/interaction.ts --network sepolia para interactuar con los contratos:

Puedes encontrar esta dirección TBA desplegada en la red Sepolia y ver los detalles de sus transacciones en el explorador aquí.
El análisis de su bytecode revela la estructura del proxy descrita anteriormente:

Sección Proxy EIP-1167
Header (10 bytes) -> 0x363d3d373d3d3d363d
Implementation Address (20 bytes) -> 311e822a099fae1ef8fc961ddf61fafd5392e7a9 (Implementation Address)
Footer (15 bytes) -> 5af43d82803e903d91602b57fd5bf3
Sección de Datos Inmutables (vinculación de cuenta)
salt (32 bytes) -> 0000000000000000000000000000000000000000000000000000000000000000
chainID (32 bytes) -> 00000000000000000000000000000000000000000000000000000000aa36a7 // chainID -> 11155111 (sepolia)
tokenContract (32 bytes) -> 0000000000000000000000006b57b7edf751829dfb2aeccf578d6d24c33a45a2 (NFT contract address)
tokenID (32 bytes) -> 0000000000000000000000000000000000000000000000000000000000000001 //token ID -> 1
El bytecode completo de la TBA muestra que la TBA recién creada en 0x97212622cbdb6f1aa96c4abceaebb2b1b47d2bbe reenvía las llamadas al contrato de implementación en 0x311e822a099fae1ef8fc961ddf61fafd5392e7a9 con la TBA vinculada al NFT:
- Dirección del contrato del NFT:
0x6b57b7edf751829dfb2aeccf578d6d24c33a45a2 - tokenID: 1
- chainID: 11155111 (testnet de Sepolia)
- salt: 0 (salt predeterminado)
Por lo tanto, cuando se llama a la función token(), lee el bytecode de la TBA:

Y luego devuelve la tupla como se ve a continuación:

Estos valores (chainId, tokenContract, tokenId) identifican de manera única el NFT al que está vinculada la cuenta, lo que facilita a los contratos o aplicaciones determinar el NFT asociado.
Manejo de la Transferencia de NFTs
Cuando se transfiere un NFT con una TBA, la propiedad de la TBA se transfiere automáticamente también. Esto ocurre porque la propiedad de la TBA es determinada de forma dinámica por el propietario actual del NFT a través de la función owner().

- La TBA retiene todos sus activos (ETH, tokens, NFTs)
- El control de la TBA cambia al nuevo propietario del NFT
- El propietario anterior pierde todo el acceso a los activos propiedad de la TBA una vez que se transfiere el NFT, a menos que esos activos se hayan transferido antes, como se ilustró en el ejemplo del juego.
La implementación utilizada en esta sección es simple. Los autores del EIP-6551 también han proporcionado una implementación de referencia actualizable en su repositorio de GitHub, que permite actualizar la lógica de la TBA apuntando a un nuevo contrato de implementación cuando sea necesario.
Prevención de Ciclos de Propiedad en las TBAs
La implementación actualizable incluye salvaguardias contra los ciclos de propiedad, que pueden ocurrir cuando una cadena de propiedad de TBAs forma un bucle.
Los ciclos de propiedad crean situaciones en las que ninguna cuenta tiene la autoridad para iniciar transacciones porque cada TBA necesita permiso de un propietario (EOA) que está atrapado en el ciclo. Esto se puede ilustrar de la siguiente manera:

Por qué estos son inseguros:
- Cada TBA necesita el permiso del propietario de su NFT (EOA) para operar
- Cuando el EOA transfiere el NFT a una de las TBAs en el ciclo, pierde todo el control porque renuncia efectivamente a la propiedad.
- En un ciclo, ninguna otra parte puede iniciar transacciones, ya que el EOA ya no posee ningún NFT en la cadena.
- Todos los activos quedan bloqueados permanentemente sin forma de romper el ciclo.
Dado que los ciclos complejos (profundidad > 1) representan bucles de propiedad profundos que pueden involucrar múltiples TBAs anidadas, detectarlos requeriría comprobaciones recursivas potencialmente infinitas, lo que hace que la detección on-chain completa sea computacionalmente inviable. Por lo tanto, es necesario que las aplicaciones implementen salvaguardias contra estos ciclos de propiedad.
Mitigación de Fraude en las TBAs
Los marketplaces de NFTs deberán implementar salvaguardias para prevenir acciones fraudulentas por parte de propietarios maliciosos de TBAs.
Considera un escenario en el que Bob posee un NFT de personaje con una TBA que contiene 0.5 ETH, 2 NFTs de dragón y $50 USDC de recompensas del juego. Bob publica el NFT de personaje en un marketplace por 1 ETH, incluyendo todos los activos en la TBA como parte del trato.
Alice, que quiere evitar empezar el juego desde cero, acepta comprar el NFT. Antes de que finalice la venta, Bob retira rápidamente los $50 USDC de la TBA.
Cuando se completa la transacción, Alice recibe el NFT del personaje con solo 0.5 ETH y 2 NFTs de dragón en la TBA, mientras que Bob termina con 1 ETH de la venta y los $50 USDC que retiró.
Para abordar actos fraudulentos como este, los marketplaces de NFTs necesitan implementar protecciones a nivel del mercado, y los contratos que implementan este estándar también deben incluir salvaguardias. Algunas estrategias de mitigación incluyen:
- Adjuntar una lista de compromisos de activos a las órdenes del marketplace (como balances particulares de ERC-20, NFTs, etc.) y anular la oferta si los activos comprometidos se eliminan antes de que se complete.
- Hacer que el marketplace tome temporalmente la propiedad del NFT durante el período de cotización (en lugar de solo derechos de aprobación), lo que impide que el vendedor manipule los activos de la TBA.
Resumen
ERC-6551 permite que los NFTs se vinculen a cuentas de contratos inteligentes (TBAs), habilitándolos para poseer activos e interactuar con protocolos. Lo logra mediante direcciones deterministas (create2), proxies mínimos personalizados modificados que almacenan datos de vinculación del NFT en el bytecode y un registro canónico, todo sin alterar los contratos de NFT existentes.
Algunos proyectos ya están aprovechando las TBAs para inventarios en el juego (poseer artículos, atuendos y equipamiento), sistemas de lealtad de la comunidad, seguimiento de reputación y otros propósitos similares.
A medida que crezca la adopción, ERC-6551 integrará aún más los NFTs en DeFi, expandiendo sus casos de uso. A pesar de su adopción actual, ERC-6551 sigue en revisión y aún no está finalizado al momento de escribir este artículo.
Para un ejemplo de implementación, consulta la documentación de Tokenbound o explora herramientas como el frontend SDK e iframe. También hay un explorador disponible para visualizar e interactuar con cuentas ERC-6551.
Referencias
Página del estándar EIP-6551
Estándar ERC-721
Proxy Mínimo EIP-1167
Slot de almacenamiento EIP-1967
Código fuente de ERC-6551
Lista de proyectos que han adoptado ERC6551
Explorador de TBA