ERC-4337 traslada la ejecución de cuentas inteligentes a una mempool alternativa donde una UserOperation no es una transacción y no tiene hash de transacción hasta que un bundler la incluye. Los bundlers exponen una superficie JSON-RPC dedicada definida por ERC-7769, que incluye eth_sendUserOperation, eth_estimateUserOperationGas, eth_getUserOperationReceipt, eth_getUserOperationByHash, eth_supportedEntryPoints y eth_chainId. Un cliente construye y firma una UserOperation, la preverifica con eth_estimateUserOperationGas, la envía con eth_sendUserOperation y luego sondea eth_getUserOperationReceipt en lugar de eth_getTransactionReceipt. Los fallos de validación y los fallos de simulación devuelven objetos de error JSON-RPC diferentes, y campos de gas como callGasLimit, verificationGasLimit, preVerificationGas y paymasterVerificationGasLimit deben configurarse correctamente, o una estimación que pasa en simulación puede revertir en cadena. Este artículo aísla el contrato RPC para que puedas integrar cualquier bundler sin SDK y luego medir su comportamiento contra tu propio endpoint.
Por qué la mempool alternativa cambia la superficie RPC
ERC-4337 introduce una mempool alternativa en la que los usuarios envían objetos UserOperation en lugar de transacciones. Una UserOperation es una intención estructurada que contiene sender, nonce, callData, límites de gas y campos opcionales de paymaster; no es una transacción Ethereum firmada y no se puede difundir con eth_sendRawTransaction. La definición autorizada de la estructura, el contrato EntryPoint y el rol del bundler se encuentra en ERC-4337: Account Abstraction Using Alt Mempool.
Como una UserOperation no es una transacción, no tiene hash de transacción en el momento del envío. En su lugar tiene un userOpHash, calculado sobre la UserOperation empaquetada y la dirección del EntryPoint y el ID de cadena. Solo después de que un bundler incluye la operación, el EntryPoint emite un UserOperationEvent que contiene ese hash, y solo entonces existe un hash de transacción subyacente. Esta distinción es la causa raíz de la mayoría de las confusiones de integración: los clientes que sondean eth_getTransactionReceipt con un userOpHash siempre recibirán null.
La mempool alternativa también es la razón por la que la superficie RPC es independiente de un nodo de propósito general. Un nodo Ethereum estándar expone métodos eth_* para transacciones y estado; un bundler expone un conjunto adicional de métodos para UserOperations. Los dos servicios pueden operarse de forma independiente, por lo que un endpoint de bundler puede estar limitado por tasa o no disponible incluso cuando tu endpoint RPC general está saludable. Para obtener antecedentes sobre cómo se expone el pool de transacciones estándar, consulta El espacio de nombres txpool de Ethereum y la mempool alternativa.
- UserOperation: una estructura de intención, no una transacción firmada.
- userOpHash: identificador determinista derivado de la operación y el EntryPoint.
- Hash de transacción: existe solo después de la inclusión y la emisión del evento.
- Endpoint del bundler: un servicio distinto de un endpoint RPC de propósito general.
El conjunto de métodos JSON-RPC del bundler y qué devuelve cada uno
ERC-7769: JSON-RPC API for ERC-4337 estandariza los contratos de métodos que un bundler debe implementar. Los métodos principales son eth_sendUserOperation, eth_estimateUserOperationGas, eth_getUserOperationReceipt, eth_getUserOperationByHash, eth_supportedEntryPoints y eth_chainId. Cada uno está envuelto en el sobre JSON-RPC 2.0 definido por la Especificación JSON-RPC 2.0, por lo que las solicitudes llevan jsonrpc, method, params e id, y las respuestas llevan result o error.
eth_sendUserOperation acepta una UserOperation y una dirección de EntryPoint y devuelve el userOpHash como cadena hexadecimal. eth_estimateUserOperationGas acepta el mismo par más una anulación de estado opcional y devuelve estimaciones de gas para callGasLimit, verificationGasLimit, preVerificationGas y, cuando hay un paymaster, paymasterVerificationGasLimit. eth_getUserOperationReceipt acepta un userOpHash y devuelve el recibo completo una vez incluido, incluido el recibo de transacción, los logs y el gas real utilizado.
eth_getUserOperationByHash devuelve la operación y su contexto de inclusión si se conoce, lo cual es útil para depurar un envío que aún no se ha incluido. eth_supportedEntryPoints devuelve las direcciones de EntryPoint que acepta el bundler, y eth_chainId devuelve el ID de cadena que sirve el bundler. Llama siempre a eth_supportedEntryPoints y eth_chainId antes de enviar, porque una discrepancia entre la versión de tu EntryPoint y el conjunto admitido por el bundler es un fallo común y silencioso.
- eth_sendUserOperation -> userOpHash (cadena hexadecimal).
- eth_estimateUserOperationGas -> callGasLimit, verificationGasLimit, preVerificationGas, paymasterVerificationGasLimit.
- eth_getUserOperationReceipt -> recibo con recibo de transacción, logs, gas real utilizado.
- eth_getUserOperationByHash -> operación más contexto de inclusión.
- eth_supportedEntryPoints -> direcciones de EntryPoint aceptadas.
- eth_chainId -> ID de cadena servido por el bundler.
Secuencia de llamadas del lado del cliente desde la construcción hasta el recibo
La secuencia de integración tiene cuatro etapas: construir y firmar, preverificar, enviar y sondear. Construir significa ensamblar los campos de la UserOperation, calcular el userOpHash y firmarlo con la clave de firma de la cuenta inteligente. Preverificar significa llamar a eth_estimateUserOperationGas para obtener los límites de gas. Enviar significa llamar a eth_sendUserOperation con la operación firmada. Sondear significa llamar repetidamente a eth_getUserOperationReceipt hasta que devuelva un recibo o superes tu tiempo de espera.
La etapa de sondeo es donde la mempool alternativa diverge más marcadamente del manejo estándar de transacciones. Como una UserOperation no tiene hash de transacción hasta la inclusión, debes sondear eth_getUserOperationReceipt con el userOpHash, no eth_getTransactionReceipt. La misma semántica de null hasta la minado que se aplica a los recibos estándar se aplica aquí, pero con una clave de identificador diferente; la mecánica del sondeo de recibos se cubre en eth_getTransactionReceipt devuelve null y sondeo de recibos.
Cuando un bundler rechaza un envío, devuelve un objeto de error JSON-RPC con code, message y data. Los fallos de validación, en los que el EntryPoint rechaza la operación durante la validación, normalmente se manifiestan como un error cuyo data contiene una razón de reversión con prefijo AA. Los fallos de simulación, en los que la propia simulación de la operación por parte del bundler falla antes del envío, pueden devolver un código diferente o un mensaje que indica fallo de simulación. Los códigos y las cadenas de mensaje exactos varían según el bundler, así que trata la forma del error como un comportamiento documentado que debes inspeccionar en lugar de un contrato fijo.
- Construir y firmar: ensamblar campos, calcular userOpHash, firmar.
- Preverificar: eth_estimateUserOperationGas.
- Enviar: eth_sendUserOperation.
- Sondear: eth_getUserOperationReceipt con el userOpHash.
- Fallo de validación vs fallo de simulación: inspecciona code, message y data del objeto de error.
Datos del paymaster y los campos de gas que deciden la inclusión
Una UserOperation lleva cuatro campos relacionados con el gas que deben configurarse antes del envío: callGasLimit, verificationGasLimit, preVerificationGas y, cuando un paymaster patrocina la operación, paymasterVerificationGasLimit. callGasLimit limita la ejecución del callData de la cuenta; verificationGasLimit limita la validación de la cuenta y del paymaster; preVerificationGas cubre el calldata y la sobrecarga del bundler; paymasterVerificationGasLimit limita la propia validación del paymaster. ERC-4337 documenta estos campos y sus roles en las fases de validación y ejecución del EntryPoint.
Los datos del paymaster se pasan en el campo paymasterAndData, cuya codificación es específica del paymaster. Un paymaster puede requerir una aprobación firmada, un pago en tokens o un patrocinio con límite temporal, y la codificación de esos datos la define la implementación del paymaster, no ERC-4337 en sí. Este es un punto de variación documentado: el campo está estandarizado, pero su contenido no.
Una estimación que pasa en simulación aún puede revertir en cadena porque la simulación se ejecuta contra una instantánea de estado específica. Si el nonce de la cuenta cambia, si el depósito de un paymaster se agota, si un saldo de tokens se mueve o si la operación se incluye en un bloque diferente con precios de gas diferentes, la validación en cadena puede fallar aunque la estimación haya tenido éxito. Trata las estimaciones como un límite inferior y deja margen, especialmente en verificationGasLimit y preVerificationGas.
- callGasLimit: limita la ejecución de callData.
- verificationGasLimit: limita la validación de la cuenta y del paymaster.
- preVerificationGas: cubre el calldata y la sobrecarga del bundler.
- paymasterVerificationGasLimit: limita la validación del paymaster.
- Codificación de paymasterAndData: específica del paymaster, varía según la implementación.
Un cliente Node.js ejecutable usando fetch simple
El siguiente ejemplo usa solo la API fetch integrada para que el contrato RPC sea visible sin un SDK. Llama primero a eth_chainId y eth_supportedEntryPoints, luego a eth_estimateUserOperationGas, después a eth_sendUserOperation y finalmente sondea eth_getUserOperationReceipt. Reemplaza el endpoint, la dirección del EntryPoint y los campos de la UserOperation con tus propios valores. El paso de firma se omite porque depende de la implementación de tu cuenta inteligente; en la práctica, firmas el userOpHash con la clave de la cuenta antes del envío.
Ten en cuenta que el ejemplo trata el endpoint del bundler como una única URL. En producción puedes apuntar a un servicio de bundler dedicado, que es un servicio distinto de un endpoint RPC de propósito general. Para obtener orientación sobre la selección de endpoints, consulta Endpoints RPC de Ethereum y selección de proveedor (RPC Assistant).
const BUNDLER_URL = 'https://your-bundler-endpoint.example';
const ENTRY_POINT = '0x0000000071727De22E5E9d8BAf0edAc6f37da032';
async function rpc(method, params) {
const res = await fetch(BUNDLER_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
const json = await res.json();
if (json.error) throw new Error(JSON.stringify(json.error));
return json.result;
}
async function main() {
const chainId = await rpc('eth_chainId', []);
const entryPoints = await rpc('eth_supportedEntryPoints', []);
console.log('chainId', chainId, 'entryPoints', entryPoints);
const userOp = {
sender: '0xYourSmartAccountAddress',
nonce: '0x0',
callData: '0x',
callGasLimit: '0x0',
verificationGasLimit: '0x0',
preVerificationGas: '0x0',
maxFeePerGas: '0x0',
maxPriorityFeePerGas: '0x0',
paymasterAndData: '0x',
signature: '0x'
};
const gas = await rpc('eth_estimateUserOperationGas', [userOp, ENTRY_POINT]);
console.log('gas estimate', gas);
const signedOp = { ...userOp, ...gas, signature: '0xYourSignature' };
const userOpHash = await rpc('eth_sendUserOperation', [signedOp, ENTRY_POINT]);
console.log('userOpHash', userOpHash);
for (let i = 0; i < 30; i++) {
const receipt = await rpc('eth_getUserOperationReceipt', [userOpHash]);
if (receipt) {
console.log('included', receipt.receipt.transactionHash);
return;
}
await new Promise(r => setTimeout(r, 4000));
}
console.log('not included within timeout');
}
main().catch(err => { console.error(err); process.exit(1); });Medir tu bundler contra tu EntryPoint
Como el comportamiento del bundler varía según el proveedor, la única forma fiable de caracterizar tu integración es medirla. Ejecuta el cliente anterior contra tu propio endpoint y EntryPoint, y registra los resultados en una tabla. La tabla siguiente es una plantilla; complétala con los valores que observes, no con números de este artículo. No asumas ninguna latencia, rendimiento o límite de tasa específico del proveedor sin medirlo tú mismo.
Mide al menos lo siguiente: el ID de cadena y los EntryPoints admitidos devueltos, las estimaciones de gas para una operación representativa, el tiempo desde eth_sendUserOperation hasta el primer eth_getUserOperationReceipt no nulo, y la forma del objeto de error para una operación deliberadamente inválida. Repite la medición en varias operaciones para ver la varianza. Si operas varios bundlers, ejecuta la misma tabla contra cada uno para poder compararlos en tus propios términos.
- Columnas de la tabla de resultados: endpoint del bundler, ID de cadena, EntryPoint admitido, estimación (callGasLimit / verificationGasLimit / preVerificationGas / paymasterVerificationGasLimit), tiempo de envío a recibo, código de error en operación inválida, mensaje de error en operación inválida, notas.
- Ejecuta cada fila al menos tres veces para observar la varianza.
- Registra el objeto de error exacto, no una paráfrasis, para poder hacer coincidir en el código.
- Compara con un segundo bundler si necesitas redundancia.
Modos de fallo y razones de reversión con prefijo AA
Los fallos de validación del EntryPoint se manifiestan como razones de reversión con el prefijo AA. Ejemplos comunes incluyen AA21 (el sender no pagó el prefondo), AA22 (expirado o no vencido), AA23 (revirtió durante la validación), AA24 (error de firma), AA25 (nonce de cuenta inválido) y AA31 (el paymaster no pagó el prefondo). Estas cadenas están documentadas en ERC-4337 y las emite el contrato EntryPoint, por lo que son consistentes entre bundlers que usan una versión de EntryPoint compatible. Cuando veas una razón con prefijo AA, el fallo está en la validación, no en la ejecución de tu callData.
La gestión de claves de nonce es una fuente frecuente de fallos en operaciones paralelas. ERC-4337 usa un nonce de 256 bits con una clave de 192 bits y una secuencia de 64 bits, lo que permite múltiples flujos de nonce independientes por cuenta. Si envías varias UserOperations en paralelo con la misma clave de nonce y secuencia, solo una puede incluirse; las demás fallan con AA25. Usa claves de nonce distintas para flujos independientes e incrementa la secuencia dentro de cada flujo. Para el modelo de nonce de transacción estándar, consulta Gestión de nonce EVM con eth_getTransactionCount.
Un bundler que devuelve un userOpHash pero nunca incluye la operación es un modo de fallo distinto. La operación puede descartarse de la mempool alternativa, puede tener un precio demasiado bajo en relación con las condiciones actuales o puede estar esperando detrás de un hueco de nonce. Sondea eth_getUserOperationByHash para ver si el bundler todavía conoce la operación, y compara tu maxFeePerGas y maxPriorityFeePerGas con las condiciones actuales. Para obtener antecedentes sobre la estimación de tarifas, consulta Estimar el precio del gas con eth_feeHistory.
Las discrepancias de ID de cadena y versión de EntryPoint son silenciosas hasta el envío. Si tu cliente apunta a una cadena pero el bundler sirve otra, o si tu dirección de EntryPoint no está en eth_supportedEntryPoints, el bundler puede rechazar la operación o devolver un error que no menciona la discrepancia. Verifica siempre eth_chainId y eth_supportedEntryPoints antes de construir la operación.
- AA21: el sender no pagó el prefondo.
- AA22: expirado o no vencido.
- AA23: revirtió durante la validación.
- AA24: error de firma.
- AA25: nonce de cuenta inválido.
- AA31: el paymaster no pagó el prefondo.
- Claves de nonce: usa claves distintas para flujos paralelos.
- userOpHash sin inclusión: revisa eth_getUserOperationByHash y los campos de tarifas.
- Discrepancia de ID de cadena y EntryPoint: verifica antes de construir.
Distinguir el comportamiento documentado de la variación del proveedor
ERC-4337 y ERC-7769 definen la estructura UserOperation, el contrato EntryPoint, la mempool alternativa y los contratos de métodos para la superficie RPC del bundler. Estos son comportamientos documentados en los que puedes confiar en implementaciones compatibles. La Especificación JSON-RPC 2.0 define el sobre del objeto de error con code, message y data, que los bundlers usan para devolver fallos de validación y simulación.
Lo que varía según el bundler o el proveedor incluye los códigos de error y las cadenas de mensaje específicos para fallos de validación y simulación, las versiones de EntryPoint admitidas, los límites de tasa y la disponibilidad del endpoint del bundler, las codificaciones de datos de paymaster aceptadas y la política de inclusión para operaciones con precio bajo. Trata estos como específicos del proveedor y mide contra tu propio endpoint en lugar de asumir un contrato fijo. Esta es la razón por la que la tabla de resultados anterior es una plantilla y no un conjunto de valores esperados.
Un endpoint de bundler es un servicio distinto de un endpoint RPC de propósito general. Puede estar limitado por tasa o no disponible de forma independiente de tu proveedor RPC estándar, y puede servir un conjunto diferente de cadenas. Si necesitas ambos, planifica dos endpoints y dos dominios de fallo. Para opciones generales de endpoints de Ethereum, consulta Endpoints RPC de Ethereum y selección de proveedor (RPC Assistant) y la página de la red Ethereum.
- Documentado: estructura UserOperation, EntryPoint, mempool alternativa, contratos de métodos, sobre de error JSON-RPC.
- Varía según el bundler: códigos y mensajes de error, EntryPoints admitidos, límites de tasa, codificaciones de paymaster, política de inclusión.
- El endpoint del bundler y el endpoint RPC general son servicios separados con dominios de fallo separados.
Limitaciones y compensaciones del modelo RPC del bundler
El modelo RPC del bundler añade una dependencia de servicio entre tu cliente y la cadena. Una UserOperation no se incluye hasta que un bundler elige incluirla, por lo que la inclusión no está garantizada solo por el envío. Esta es una compensación deliberada de la mempool alternativa: permite patrocinio y agrupación, pero también significa que tu cliente debe manejar un estado pendiente que no tiene hash de transacción ni semántica de reemplazo estándar.
La estimación de gas es orientativa. Una estimación que pasa en simulación aún puede revertir en cadena porque el estado cambia entre la simulación y la inclusión. El patrocinio del paymaster añade otra dependencia: si el depósito del paymaster se agota o su política cambia, la operación falla la validación aunque tu cuenta esté financiada. La gestión de claves de nonce añade complejidad para operaciones paralelas, y las razones de reversión con prefijo AA requieren que asignes cadenas de error a pasos de remediación.
Operativamente, debes tratar el endpoint del bundler como un dominio de disponibilidad separado. Puede estar limitado por tasa o no disponible de forma independiente de tu endpoint RPC general, y su conjunto de EntryPoints admitidos puede cambiar. Si necesitas redundancia, ejecuta la tabla de resultados contra varios bundlers y enruta según el comportamiento medido en lugar de suposiciones. Para patrones de fiabilidad relacionados, consulta Idempotencia JSON-RPC y seguridad ante solicitudes duplicadas.
- La inclusión no está garantizada por el envío.
- Las estimaciones son orientativas y pueden divergir de la ejecución en cadena.
- El patrocinio del paymaster añade una segunda dependencia.
- La gestión de claves de nonce añade complejidad para operaciones paralelas.
- El endpoint del bundler es un dominio de disponibilidad separado.
Próximos pasos para integrar una cuenta inteligente con un bundler
Empieza llamando a eth_chainId y eth_supportedEntryPoints contra el bundler que elijas, luego ejecuta el cliente Node.js anterior con una operación mínima y completa la tabla de resultados. Una vez que tengas una línea base, añade el patrocinio del paymaster y mide cómo cambian los campos de gas. Después prueba deliberadamente las rutas de fallo: envía una operación con una firma incorrecta para observar la forma del error AA24, y envía dos operaciones con la misma clave de nonce para observar AA25.
Para producción, envuelve el cliente en lógica de reintento y tiempo de espera, y sondea eth_getUserOperationReceipt con un tiempo de espera acotado en lugar de indefinidamente. Mantén un endpoint de bundler de respaldo si la disponibilidad importa, y vuelve a ejecutar la tabla de resultados periódicamente porque el comportamiento del proveedor puede cambiar. Si necesitas acceso RPC general a Ethereum junto con tu bundler, revisa Precios de RPC y el Servicio de API para planificar tu topología de endpoints.
Para obtener un contexto más amplio sobre patrones de integración RPC de Ethereum, el centro de aprendizaje de OnFinality recopila guías relacionadas sobre recibos, nonces, estimación de tarifas e idempotencia. Úsalas junto con este artículo para construir un cliente completo que maneje tanto la ruta de transacción estándar como la ruta de la mempool alternativa de ERC-4337.
- Verifica primero el ID de cadena y los EntryPoints admitidos.
- Ejecuta el cliente y completa la tabla de resultados.
- Añade el patrocinio del paymaster y vuelve a medir los campos de gas.
- Prueba deliberadamente las rutas de fallo AA24 y AA25.
- Añade reintentos, tiempos de espera y un bundler de respaldo si es necesario.