Ethereum: de historia a labs
Artículo 37 de 48
77%
completado al llegar aquí
Sistemas distribuidosNota de investigación17 min de lectura
ERC-4337: UserOperation, bundlers y EntryPoint
Cómo ERC-4337 crea smart accounts sin hard fork: UserOperation, bundler, EntryPoint y paymaster, con los riesgos que hereda un equipo de custodia.
Ruta Ethereum: 0/48
Prerequisitos recomendados
Si alguno de estos conceptos todavía está medio nebuloso, empieza por aquí. Te ahorra tropiezos y un par de cejas fruncidas.
Cuentas, claves y direcciones sin humo
Cómo una clave secp256k1 se convierte en dirección con Keccak-256, qué valida el checksum EIP-55 y qué cambia EIP-7702 para quien custodia claves.
Blockchain21 min de lectura→Una transacción Ethereum por dentro
Campos de una transacción tipo 2, cómo EIP-1559 reparte el gas entre quema, propina y devolución, y qué prueba realmente un receipt con status 0 o 1.
Blockchain17 min de lectura→
Imagina que tu equipo custodia fondos y quiere una regla simple: cualquier retiro mayor que cierto monto necesita dos firmas, hay un límite diario y la empresa paga el gas para que el usuario no tenga que comprar ETH. Con una cuenta normal de Ethereum esa regla no se puede expresar. La red solo acepta una transacción si trae una firma ECDSA válida de una clave, el nonce correcto y saldo suficiente. No hay lugar para "dos de tres" ni para "hasta 10.000 por día".
ERC-4337 resuelve eso sin tocar el protocolo base. En lugar de una nueva transacción, define un objeto llamado UserOperation, una red paralela donde circula y un contrato único, el EntryPoint, que decide qué operaciones se validan y ejecutan. [1]
Brújula de lectura
Actor, evidencia y estado
Actor
Cuenta, bundler y paymasterLa cuenta define su regla de firma; el bundler incluye; el paymaster puede pagar.
Evidencia
userOpHash y receipt de handleOpsEl hash de la UserOperation no es el hash de la transacción que la incluye.
Estado
Depósitos y nonces en EntryPointEl EntryPoint guarda depósitos, stake y la secuencia de nonces de cada cuenta.
Preguntas guía
- ¿Quién paga el gas de una operación y en qué momento se le cobra?
- ¿Qué contrato decide si una firma es válida: el protocolo, el EntryPoint o la cuenta?
- ¿Qué identificador debe guardar tu sistema para rastrear una operación de punta a punta?
- ¿Qué pasa con la operación si el único bundler que usas deja de responder?
El cuello de botella: la regla de validez fija de una EOA
Una EOA (externally owned account, cuenta controlada por una clave privada) tiene una sola forma de autorizar: firmar con esa clave. Quien tiene la clave puede hacer todo; quien la pierde no puede hacer nada. La página de ethereum.org lo resume así y agrega que una clave robada da acceso inmediato a todos los fondos. [2]
Una smart account (cuenta inteligente) es un contrato que guarda los fondos y define en código qué cuenta como autorización válida. Puede pedir varias firmas, aceptar otro esquema criptográfico, limitar montos o permitir recuperar la cuenta con claves de respaldo. El problema histórico era que un contrato no puede iniciar una transacción: alguien con una EOA tenía que enviarla por él.
ERC-4337 ataca ese cuello de botella separando tres cosas que en una EOA vienen pegadas: validar, pagar y ejecutar. La especificación lo dice en su motivación: abstraer la validación permite multisig y recuperación; abstraer el pago permite que un tercero pague o que se pague con tokens; abstraer la ejecución permite agrupar llamadas. Y lo hace sin cambios de consenso, porque los autores no querían esperar a un hard fork. [1]
Anatomía de una UserOperation
Una UserOperation es una "pseudo-transacción": describe lo que la cuenta quiere hacer, pero no es una transacción de Ethereum. Se envía a un bundler con el método JSON-RPC eth_sendUserOperation. Sus campos principales son:
| Campo | Para qué sirve |
|---|---|
sender | La smart account que actúa. |
nonce | Anti-replay. Son 256 bits: 192 de "key" y 64 de "sequence". |
factory y factoryData | Despliegan la cuenta si todavía no existe. |
callData | Lo que la cuenta ejecutará, por ejemplo una llamada execute. |
callGasLimit, verificationGasLimit | Gas para ejecución y para validación. |
preVerificationGas | Gas extra para compensar al bundler por costos que el contrato no mide. |
maxFeePerGas, maxPriorityFeePerGas | Precio máximo, con semántica análoga a la de EIP-1559. |
paymaster y sus campos | Contrato que paga en lugar de la cuenta, si existe. |
signature | Bytes que la cuenta interpreta como quiera. |
El detalle más importante para custodia está en la firma. El protocolo no define qué es una firma válida: lo define el código de la cuenta. Lo que sí exige la especificación es que la firma dependa del chainId y de la dirección del EntryPoint, para que una operación firmada en una red no se pueda reproducir en otra ni contra otra versión del EntryPoint. El userOpHash es un hash tipado EIP-712 sobre la operación (sin la firma), el EntryPoint y el chainId. [1]
El nonce partido en dos también tiene uso práctico. Para cada "key" de 192 bits, el EntryPoint exige una secuencia que crece de uno en uno. Una cuenta puede usar la key 0 para operaciones normales y otra key para un canal administrativo, por ejemplo rotar firmantes, sin que una cola bloquee a la otra. Si la cuenta quiere el comportamiento clásico de un solo nonce secuencial, debe exigir que la key sea cero.
Traza de ejecución: del bundler a handleOps
Sigamos una operación concreta: la cuenta quiere transferir 500 USDC y la empresa paga el gas con un paymaster.
El bundler es un nodo que recibe operaciones, las agrupa y envía una transacción normal que llama a EntryPoint.handleOps(ops, beneficiary). La especificación le pide validar tres veces: al recibir la operación, al armar el bundle y justo antes de enviarlo. Cada validación es una simulación, es decir, una llamada de solo lectura que ejecuta la parte de validación de la cuenta y del paymaster. Si la simulación revierte, la operación se descarta y no se propaga a otros bundlers. [1]
Dentro de handleOps hay dos bucles. En el bucle de verificación, para cada operación el EntryPoint crea la cuenta si hace falta, calcula el costo máximo posible, llama a validateUserOp en la cuenta y, si hay paymaster, llama a validatePaymasterUserOp y revisa que el depósito alcance. En el bucle de ejecución llama a la cuenta con su callData, invoca postOp en el paymaster si este lo pidió, devuelve el gas cobrado de más y al final paga todas las comisiones a la dirección beneficiary del bundler.
Hay una penalización que conviene conocer antes de estimar gas: si queda sin usar al menos 40.000 de gas de callGasLimit o de paymasterPostOpGasLimit, se cobra el 10 % de ese sobrante. Existe para que nadie reserve mucho espacio en el bundle y deje fuera a otras operaciones. Sobrestimar "por si acaso" tiene costo.
Recorrido de una UserOperation hasta el EntryPoint
Fuentes:
[1]Cómo leer esta figura
La figura tiene dos franjas. La franja superior, fuera de la cadena, muestra la wallet que firma la UserOperation y la envía con eth_sendUserOperation al bundler, que la simula tres veces: al recibirla, al armar el bundle y antes de enviarlo. Una flecha baja a la franja inferior, en la cadena, donde una transacción llama a EntryPoint.handleOps. El EntryPoint ejecuta primero un bucle de verificación con tres pasos: desplegar la cuenta si hay initCode, llamar a validateUserOp en la cuenta y llamar a validatePaymasterUserOp revisando el depósito del paymaster. Luego ejecuta un bucle de ejecución: llamar a la cuenta con callData, llamar a postOp del paymaster y reembolsar el gas sobrante. Al final paga las comisiones a la dirección beneficiary del bundler.
Para tu sistema esto significa que hay dos identificadores. El userOpHash identifica la operación; el hash de la transacción identifica el bundle que la incluyó. Varias operaciones pueden compartir una misma transacción, y una operación puede fallar en ejecución aunque la transacción del bundle tenga éxito. La especificación introduce eth_getUserOperationReceipt para consultar el resultado por userOpHash, aunque deja fuera de su alcance la definición completa de la API JSON-RPC.
La interfaz mínima que implementa la cuenta
La cuenta solo necesita una función obligatoria. Según la especificación debe comprobar que quien llama es un EntryPoint de confianza, verificar la firma sobre userOpHash, revertir ante cualquier error que no sea una firma incorrecta y pagar al EntryPoint al menos missingAccountFunds. Ante una firma incorrecta, la recomendación (un "SHOULD" del estándar) es devolver 1 (SIG_VALIDATION_FAILED) sin revertir y sin cortar el flujo antes de tiempo, para que la estimación de gas de la validación siga funcionando. El valor devuelto también puede empaquetar una ventana de validez (validAfter, validUntil).
El siguiente contrato es ilustrativo: muestra la forma de esa interfaz con un único dueño. No verifica la maleabilidad de firmas ni tiene recuperación, así que no debe usarse con fondos reales.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
struct PackedUserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
bytes32 accountGasLimits;
uint256 preVerificationGas;
bytes32 gasFees;
bytes paymasterAndData;
bytes signature;
}
/// Ilustrativo: cuenta de un solo dueño para leer la interfaz de ERC-4337.
contract MinimalAccount {
uint256 internal constant SIG_VALIDATION_FAILED = 1;
address public immutable entryPoint;
address public immutable owner;
constructor(address entryPoint_, address owner_) {
entryPoint = entryPoint_;
owner = owner_;
}
modifier onlyEntryPoint() {
require(msg.sender == entryPoint, "solo EntryPoint");
_;
}
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external onlyEntryPoint returns (uint256 validationData) {
validationData = _recover(userOpHash, userOp.signature) == owner ? 0 : SIG_VALIDATION_FAILED;
if (missingAccountFunds > 0) {
(bool ok, ) = payable(msg.sender).call{value: missingAccountFunds}("");
(ok); // el EntryPoint comprueba después que el depósito alcance
}
}
function execute(address target, uint256 value, bytes calldata data) external onlyEntryPoint {
(bool ok, bytes memory ret) = target.call{value: value}(data);
if (!ok) {
assembly {
revert(add(ret, 32), mload(ret))
}
}
}
function _recover(bytes32 digest, bytes calldata sig) internal pure returns (address) {
if (sig.length != 65) return address(0);
bytes32 r = bytes32(sig[0:32]);
bytes32 s = bytes32(sig[32:64]);
uint8 v = uint8(sig[64]);
return ecrecover(digest, v, r, s);
}
receive() external payable {}
}Fíjate en onlyEntryPoint sobre execute. Si una cuenta deja execute abierta a cualquiera, toda la validación anterior sobra: cualquier contrato podría mover los fondos sin pasar por validateUserOp. La sección de seguridad de la especificación pide justamente que las demás funciones de la cuenta solo acepten llamadas del EntryPoint (o de la propia cuenta).
Paymasters, depósitos y stake
Un paymaster es un contrato que acepta pagar el gas de otra cuenta. La empresa del ejemplo despliega uno que patrocina solo a sus usuarios, o uno que cobra el equivalente en un token ERC-20. Durante la verificación, el EntryPoint llama a validatePaymasterUserOp(userOp, userOpHash, maxCost). Si el paymaster devuelve un context no vacío, después de ejecutar se llama a postOp con el costo real, aun cuando la operación revirtió: en ese caso el paymaster igual paga el gas. [1]
Hay dos saldos distintos en el EntryPoint y conviene no confundirlos. El depósito es ETH que el EntryPoint descuenta para pagar gas; se gestiona con depositTo, balanceOf y withdrawTo. El stake es ETH bloqueado con addStake(unstakeDelaySec), que solo se retira tras unlockStake y la espera fijada. El stake no paga gas: sirve como garantía para que los bundlers le permitan al paymaster usar más almacenamiento durante la validación.
Desde el punto de vista operativo, un paymaster es una cuenta corriente con política. Si su depósito se agota, todas las operaciones que dependen de él dejan de validarse al mismo tiempo. Si su política de firma vive en un servidor, ese servidor pasa a ser parte del camino crítico de cada retiro patrocinado.
Por qué el bundler desconfía: reglas de validación
En una transacción normal, el nodo puede decidir si es válida con datos estáticos: firma, nonce, saldo. Una UserOperation no: su validez depende de código EVM que lee estado, y otra operación del mismo bundle podría cambiar ese estado e invalidarla después de simulada. Si eso pasa, el bundler paga el gas del bundle sin cobrar.
Por eso existe ERC-7562, todavía en estado de revisión, que fija las reglas que deben cumplir la cuenta, la factory y el paymaster durante la validación: qué opcodes pueden usar, a qué almacenamiento pueden acceder y cuándo se exige stake. Su principio es que, una vez admitida en el mempool, una operación debe garantizar el pago de comisiones, como ocurre con una EOA. [3]
ERC-4337 añade límites concretos que el bundler revisa antes de simular: verificationGasLimit y el de verificación del paymaster deben ser menores que MAX_VERIFICATION_GAS (500.000), y preVerificationGas debe cubrir el costo del calldata más PRE_VERIFICATION_OVERHEAD_GAS (50.000). Además, solo se acepta una operación por sender en el mempool y en cada bundle, salvo que la cuenta tenga stake. Para un servicio que procesa muchos retiros desde la misma cuenta, esta regla define el throughput real.
Riesgo operativo para un equipo de custodia
El EntryPoint concentra la confianza. La propia especificación dice que debe auditarse y verificarse formalmente porque es el punto central de todo el sistema, y enumera dos propiedades mínimas: que solo llame a una cuenta con su callData si validateUserOp de esa cuenta pasó, y que si cobra la validación también ejecute la llamada. Elegir una versión del EntryPoint es elegir el contrato del que depende cada fondo.
La dirección de una cuenta se conoce antes de desplegarla. Se espera que la factory use CREATE2, y es importante que la dirección dependa de las credenciales iniciales; así nadie puede adelantarse y desplegar en esa dirección una cuenta con otro dueño. Un detalle que confunde: si la cuenta ya existe y la operación trae initCode, ese código se ignora.
Las claves de sesión no forman parte del estándar. La documentación de ERC-4337 aclara que se implementan wallet por wallet, dentro de validateUserOp, mediante módulos o plugins. [4] Auditar una smart account significa auditar ese módulo, no solo la interfaz.
ERC-4337 también admite EOAs que delegaron código con EIP-7702. En ese caso el campo factory lleva el marcador 0x7702, el bundler envía la autorización en una transacción de tipo set-code y el userOpHash incluye la dirección del contrato delegado. [1] [5] Lo vemos en el capítulo siguiente.
Antes de aprobar una smart account para custodia
0 de 6 revisados
Lab propuesto: simular una UserOperation patrocinada
Objetivo: desplegar localmente un EntryPoint, una cuenta mínima y un paymaster de prueba, y observar cada etapa. La documentación de ERC-4337 explica cómo desplegar el EntryPoint en una red local desde el repositorio oficial de referencia, con una sal fija de CREATE2 para obtener la misma dirección que en otras redes. [6]
La validación observable es concreta: una operación con firma correcta deja un evento del EntryPoint y descuenta el depósito del paymaster; la misma operación con la firma de otra clave devuelve SIG_VALIDATION_FAILED en la simulación y nunca llega al bundle. Como teardown, basta detener la red local. El riesgo de llevarlo tal cual a mainnet es claro: la cuenta de ejemplo no tiene recuperación ni defensas contra firmas maleables.
Conexiones dentro de la serie
- Antes: EIP-4844 y blobs cierra el bloque de escalado que precede a este capítulo.
- Base necesaria: cuentas, claves y direcciones y transacciones, gas y receipts.
- Después: EIP-7702 muestra cómo una EOA puede ejecutar código de smart account.
- Relacionado: aprobaciones, permit y riesgo de wallet para el lado de las firmas que autorizan gastos.
Bibliografía académica
- [1]
V. Buterin, Y. Weiss, D. Tirosh, S. Nacson, A. Forshtat, K. Gazso y T. Hess, "ERC-4337: Account Abstraction Using Alt Mempool," Ethereum Improvement Proposals, no. 4337, 2021.
Especificación final: UserOperation, EntryPoint, paymasters, simulación y consideraciones de seguridad.
- [2]
ethereum.org, "Account abstraction," Ethereum roadmap, actualizado el 24 de junio de 2026.
Límites de las EOAs y beneficios de las smart contract wallets.
- [3]
Y. Weiss, D. Tirosh, A. Forshtat y S. Nacson, "ERC-7562: Account Abstraction Validation Scope Rules [Review]," Ethereum Improvement Proposals, no. 7562, 2023.
Reglas de opcodes, almacenamiento, stake y reputación para la fase de validación.
- [4]
ERC-4337 Documentation, "Session Keys & Delegation."
Las claves de sesión no están estandarizadas y dependen de cada wallet.
- [5]
V. Buterin, S. Wilson, A. Dietrichs y lightclient, "EIP-7702: Set Code for EOAs," Ethereum Improvement Proposals, no. 7702, 2024.
Transacción de tipo 0x04 que escribe un indicador de delegación en el código de una EOA.
- [6]
ERC-4337 Documentation, "The EntryPoint Contract."
Responsabilidades del EntryPoint y despliegue local con CREATE2.
Rutas vecinas
Si este tema te abrió hambre, sigue por aquí.
Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.
Series relacionadas
HotStuff: de cero a estado del arte
Ruta progresiva para entender consenso BFT moderno: quórums, sincronía parcial, locks, taxonomía one/two/three-chain, QC/highQC, multipipeline, view-change, responsiveness, AggQC, Fast-HotStuff, sync y frontera DAG-BFT.
Substrate: del runtime al consenso propio
Serie de 20 capítulos, de neófito a experto, sobre Substrate (Polkadot SDK): runtime en Wasm, FRAME, extrinsics, estado, pesos, actualizaciones sin fork, Aura, BABE, GRANDPA, sincronización, parachains, XCM, Sassafras, BEEFY y cómo conectar un consenso BFT propio a un nodo.
Ruta práctica: activos digitales en la operación
Nueve módulos, de menor a mayor dependencia, para decidir cómo usar custodia, wallets, llaves, firmas multifirma y MPC, AML/KYT y stablecoins con Fireblocks, BitGo, Kaleido, Circle y Tether.
Labs para convertir lectura en práctica
Prueba de trabajo y reajuste de dificultad
Laboratorio determinista para medir el costo de la prueba de trabajo y ver cómo el reajuste por altura de bloque estabiliza el tiempo entre bloques.
Consensus benchmark
Banco reproducible para comparar consenso BFT y documentar los trade-offs de HotStuff-2.
Ethereum local: Anvil, wallet y primera transacción
Laboratorio base para levantar una red local, crear cuentas, firmar transacciones y observar receipts sin gastar ETH real.
RPC y Cast: leer Ethereum con lupa
Ejercicios con JSON-RPC y Cast para consultar bloques, nonces, gas, receipts, logs y errores reproducibles.
Sigue leyendo
Ideas conectadas con este artículo
Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.
EIP-7702: cuando una EOA aprende trucos de smart account
Set code for EOAs, delegación persistente, relación con account abstraction y riesgos de UX.
Cuentas, claves y direcciones sin humo
EOAs, contract accounts, claves privadas, direcciones, nonces y el primer mapa mental de identidad en Ethereum.
Ethereum: mapa de lectura antes de tocar Solidity
Ruta completa para aprender Ethereum desde historia y arquitectura hasta EVM, proof-of-stake, L2, seguridad, DeFi y labs reproducibles.
Antes de Ethereum: Bitcoin, scripts y la máquina mundial
Historia inicial de Ethereum: qué problema vio el whitepaper, por qué los contratos generales importaban y qué cambió frente a Bitcoin.