← Blog
28 de septiembre, 2026 · 14 min

Smart Sessions vs x402: dónde chocan las session keys de ERC-7579 con el pago

Smart Sessions le da a una smart account topes de gasto, límites de uso y ventanas de tiempo. Es justo lo que necesita un agente autónomo antes de darle una wallet. Pero aplica esas reglas sobre userOps de ERC-4337, y un pago x402 nunca se convierte en uno.

El problema de permisos para agentes que pagan tiene una forma conocida. Quieres darle a un agente una key que pueda gastar, pero solo hasta cierto monto, solo en ciertas cosas, solo por un tiempo. En smart accounts EVM la respuesta estándar hoy es Smart Sessions, un módulo de session keys construido en conjunto por Rhinestone y Biconomy para cuentas ERC-7579. Es uno de los módulos de permisos más desplegados del ecosistema de account abstraction.

Ya escribimos sobre el camino de Coinbase Spend Permissions y sobre Zodiac Roles en Safe. Smart Sessions es la tercera pata de ese banco, y la más cercana al mainstream de ERC-4337. Así que leímos el bytecode desplegado, los contratos de policy y las tres auditorías. El módulo es cuidadoso y está bien revisado. Pero cuando lo apuntas a x402, la frontera entre ambos protocolos es más delgada de lo que parece, y pasa por el único lugar donde las session keys son más difíciles de razonar: la firma.

Qué es Smart Sessions en realidad

Smart Sessions es un módulo validador de ERC-7579. Lo instalas en una smart account modular y luego creas sesiones: cada sesión empareja una session key (cualquier ISessionValidator, desde un firmante ECDSA simple hasta un passkey) con un conjunto de policies que restringen qué puede hacer esa key. Los autores son Filipp Makarov de Biconomy y zeroknots.eth de Rhinestone; la licencia es AGPL-3.0.

El módulo canónico está desplegado en la misma dirección en cada chain importante. Verificamos el bytecode nosotros mismos:

// módulo SmartSession, código idéntico en Base, Ethereum, Optimism, Arbitrum
const SMART_SESSIONS = '0x00000000008bDABA73cD9815d79069c247Eb4bDA';

// cast codesize en cada chain devuelve los mismos 23,608 bytes
$ cast codesize 0x00000000008bDABA73cD9815d79069c247Eb4bDA --rpc-url https://mainnet.base.org
// 23608

Las policies son singletons separados, cada uno también desplegado en una dirección vanity. En Base el ERC20SpendingLimitPolicy vive en 0x00000088D48cF102A8Cdb0137A9b173f957c6343 (3,116 bytes), el UniversalActionPolicy en 0x0000006DDA6c463511C4e9B05CFc34C1247fCF1F y el SudoPolicy en 0x0000003111cD8e92337C100F22B7A9dbf8DEE301. El struct Session de una sesión lleva tres baldes de policy: userOpPolicies (chequeado contra el userOp completo), actions (ActionData por target-más-selector) y erc7739Policies (el camino de firma ERC-1271, más sobre esto abajo).

Para un presupuesto de agente la lista corta es corta. ERC20SpendingLimitPolicy topea cuántos tokens puede mover la sesión. UsageLimitPolicy topea cuántas operaciones puede hacer. TimeFramePolicy acota la ventana de validez. ValueLimitPolicy topea el value nativo. Sobre el papel esta es la capa de presupuesto perfecta para un agente que paga por llamada.

La policy de límite de gasto no habla EIP-3009

Aquí está la primera grieta. El ERC20SpendingLimitPolicy es una action policy: inspecciona la calldata de una llamada a token y descuenta un balance restante. Leímos su helper _isTokenTransferOrApprove. Se ramifica en exactamente cuatro function selectors:

// selectors que ERC20SpendingLimitPolicy reconoce
transfer(address,uint256)              // 0xa9059cbb
transferFrom(address,address,uint256)  // 0x23b872dd
approve(address,uint256)               // 0x095ea7b3
increaseAllowance(address,uint256)     // 0x39509351

// cualquier otra cosa devuelve (isTokenTransfer=false) -> VALIDATION_FAILED

x402 no usa ninguno de esos. El camino de settlement EIP-3009 sobre el que corre x402 llama a transferWithAuthorization (selector 0xe3ee160e) o receiveWithAuthorization (0xef55bec6). Ninguno está en la lista. Si de algún modo enrutaras un settlement x402 por la cuenta como una action, la spending policy vería un selector desconocido, devolvería que no es un token transfer, y fallaría la validación. La capa de presupuesto no puede medir el gasto x402, porque no reconoce la forma de un pago x402.

Eso es un síntoma, no la enfermedad. El punto más profundo es que el settlement x402 no es algo que la cuenta ejecute en absoluto.

x402 nunca se convierte en un userOp

Recorramos qué pasa cuando un agente paga por una llamada de API medida bajo x402. El agente firma una autorización EIP-3009 off-chain: un mensaje typed-data que dice "este firmante autoriza mover N USDC al resource server, válido entre estos timestamps, con este nonce." El agente entrega esa firma al resource server o a su facilitator. El facilitator luego envía transferWithAuthorization al contrato de USDC como una transacción común, pagando su propio gas, y USDC mueve el dinero.

Nota quién hace qué. La cuenta nunca emite una transacción. No hay userOp. El EntryPoint no está involucrado. La transacción del facilitator es una llamada del facilitator a USDC, y USDC extrae del firmante usando la firma. Toda policy de Smart Sessions que opera sobre un userOp —la gas policy, la usage-limit policy, la spending policy de ERC20 en su forma de userOp— simplemente nunca se invoca, porque no hay userOp que validar.

Así que Smart Sessions, el portero de la ejecución, no controla la ejecución de x402. La única superficie donde ambos protocolos se tocan es la firma. Y esa superficie está gobernada por ERC-1271, que para Smart Sessions significa ERC-7739.

La frontera de la firma: ERC-1271 y ERC-7739

Cuando el agente que paga es una smart account y no un EOA, la autorización EIP-3009 no puede ser una firma ECDSA cruda —un contrato no tiene private key—. USDC maneja esto. Leímos el EIP3009.sol de Circle: su _requireValidSignature usa SignatureChecker.isValidSignatureNow, que cae a ERC-1271 cuando el firmante es un contrato:

// EIP3009._requireValidSignature del FiatToken de Circle
SignatureChecker.isValidSignatureNow(
    signer,
    MessageHashUtils.toTypedDataHash(_domainSeparator(), dataHash),
    signature
);

Entonces, para un agente smart-account, USDC termina llamando a account.isValidSignature(digest, signature), donde digest es el hash typed-data plano de TransferWithAuthorization de EIP-3009, construido con el domain separator propio de USDC. Si la cuenta enruta ERC-1271 a Smart Sessions, la llamada aterriza en isValidSignatureWithSender. Y esa función no espera una firma plana.

Smart Sessions implementa ERC-7739, el esquema de EIP-712 anidado que evita que una firma hecha para una cuenta se reproduzca contra otra cuenta que comparte el mismo firmante. Reenvuelve el hash entrante dentro de un struct TypedDataSign que liga el domain propio de la cuenta, y luego chequea la firma contra eso. El formato de wire que el módulo espera no es r‖s‖v. Es:

// lo que SmartSession.isValidSignatureWithSender espera
permissionId (32 bytes)
  ‖ signatureForSessionValidator
  ‖ APP_DOMAIN_SEPARATOR
  ‖ contents            // el struct hash original
  ‖ contentsType        // ej. "TransferWithAuthorization(address from,...)"
  ‖ uint16(contentsType.length)

El módulo también controla dos cosas más allá de la criptografía. Chequea que el permissionId sea una sesión habilitada, y que el nombre del content —el tipo de lo que se firma— esté en la lista allowedERC7739Content de esa sesión. Solo se soporta el workflow TypedDataSign; el camino PersonalSign fue removido y ahora devuelve false. Y, según un fix de auditoría que discutimos abajo, debe haber al menos una policy ERC-1271 habilitada para que el chequeo pase.

Compara eso con cómo firman realmente los clientes x402. Los SDK compradores apuntan a EOAs: construyen el typed data de EIP-3009 y llaman al signTypedData de viem, produciendo una firma plana de 65 bytes sobre el digest propio de USDC. Esa firma, metida en el envelope de Smart Sessions, no tiene nada del framing de ERC-7739. El assembly del módulo leería basura en los últimos dos bytes como el largo del contentsType, fallaría al reconstruir un hash coincidente, y devolvería el valor mágico de fallo. USDC entonces rechazaría el pago.

El desajuste en una línea — los clientes x402 firman un digest EIP-3009 plano; el camino ERC-1271 de Smart Sessions solo acepta una firma envuelta en ERC-7739 cuyo content type esté pre-registrado en la sesión. Los dos no se encuentran a menos que se le enseñe al cliente x402 a producir el wrapper y la sesión se aprovisione con TransferWithAuthorization en su content permitido.

Esto no es un bug en ninguno de los dos protocolos. ERC-7739 está haciendo exactamente su trabajo —y ese trabajo importa enormemente para los agentes, que es todo el punto de la siguiente sección—. Pero significa que un agente smart-account no puede pagar x402 a través de Smart Sessions con un cliente x402 de fábrica. Alguien tiene que construir el envoltorio ERC-7739 para el content TransferWithAuthorization del lado de la firma, y aprovisionar ese content type en la sesión al momento de habilitarla.

Por qué la protección de replay no es opcional para los agentes

Podrías tentarte a saltear ERC-7739 y enrutar x402 por un validador ERC-1271 más simple que solo chequee el digest crudo. La historia de auditorías dice que no lo hagas. En octubre de 2024, Cantina marcó un hallazgo de severidad Alta en exactamente este código: la lógica de ERC-7739 usaba la dirección propia del módulo SmartSession como verifyingContract en el struct anidado, en vez de la dirección de la smart account.

"Este enfoque anula el propósito principal de usar ERC-7739, ya que hace que todas las cuentas modifiquen sus hashes de la misma forma predecible. Como resultado, dos smart accounts que comparten un firmante podrían ser vulnerables a replay de firma." — Cantina, review de Smartsessions Core, hallazgo 3.1.4

Léelo en el contexto de agentes. Las flotas de agentes son el despliegue obvio: un operador, una key de firma de sesión, muchas smart accounts. Ese es precisamente el caso "múltiples cuentas comparten un firmante" para el que existe ERC-7739. Si ese bug hubiera salido, una firma que un agente produjo para pagar desde la cuenta A podría reproducirse para extraer la misma autorización de la cuenta B. Se arregló (PRs 64 y 77) para que la dirección de la cuenta ligue el hash. Pero es un recordatorio de que el wrapper de replay es estructural para cualquiera que corra más de un agente con una key compartida —lo cual es, en la práctica, todo el que corre agentes a escala—.

La misma review encontró un borde relacionado: _erc1271IsValidSignatureNowCalldata originalmente devolvía false cuando una sesión tenía cero policies ERC-1271, y el fix (PR #130) hizo el diseño explícito —debe instalarse al menos una policy ERC-1271 para que la validación de firma pase—. Si aprovisionas una sesión para firmar x402 y olvidas la policy 1271, cada firma de pago falla en silencio. Es un precipicio de configuración que conviene conocer antes de que le cueste una tarde a un agente en "por qué me está rechazando USDC."

Un presupuesto que topea el eje equivocado

Un hallazgo más aterriza de lleno en la economía del agente. En julio de 2025, ChainLight auditó las policies y calificó de severidad Media un hueco en SimpleGasPolicy: topeaba el límite de gas de un userOp pero no el precio del gas.

"El SimpleGasPolicy solo limita el gas limit y no restringe el gas price, permitiendo que las sesiones gasten más ETH del que el usuario pretendía." — ChainLight, Rhinestone Smart Sessions Security Audit, SmartSessions-001

Un presupuesto de gas que acota los pasos de cómputo pero no el precio no es un tope de gasto —una sesión podría quemar mucho más ETH del pretendido con un gas price alto—. Se parchó para multiplicar precio por límite. La lección generaliza más allá de esta policy: para un agente autónomo, el eje que importa es el dinero que sale, denominado en la unidad que el agente realmente gasta. Una policy que topea unidades de cómputo, o cantidad de llamadas, o cantidad de tokens, es solo un proxy de la cifra en dólares que le importa al operador. Smart Sessions te da varios proxies; ninguno es "no más de $50 de USDC esta semana" salvo que los compongas con cuidado —y, como vimos, ninguno ve el gasto x402 en absoluto—.

Qué significa para LLM4Agents

LLM4Agents liquida inferencia por llamada en stablecoins sobre x402 y EIP-3009. Una porción grande y creciente de los agentes que llamarán al gateway son, o serán, smart accounts ERC-7579 —ahí es donde el ecosistema de account abstraction se está estandarizando, y Smart Sessions es su módulo de session keys por defecto—. Así que esta frontera está directamente en nuestro camino.

La conclusión sin rodeos: Smart Sessions no controla de forma nativa un pago x402. Sus policies de ejecución nunca se disparan, porque el settlement x402 es una transacción del facilitator, no un userOp. Su spending policy no reconoce los selectors de EIP-3009. Y su única superficie relevante —la validación de firma ERC-1271— solo acepta una firma envuelta en ERC-7739 sobre un content type pre-registrado, que ningún cliente x402 de fábrica produce. Un agente smart-account que "tiene Smart Sessions instalado" no está, por ese hecho, limitado en gasto en el gateway.

Eso es una amenaza si asumimos lo contrario, y una oportunidad si construimos para ello. Significa que nuestro gateway nunca debería tratar "el agente usa una smart account con un módulo de sesión" como evidencia de un tope de gasto. El tope que protege los ingresos de LLM4Agents y la tesorería del agente es el que se aplica sobre el pago mismo —el maxAmountRequired de x402, la ventana validBefore de EIP-3009, el balance por agente de nuestro lado— no el que un módulo de cuenta upstream pueda o no aplicar. Esto refleja lo que encontramos al auditar la delegación ERC-7710: la capa de permisos on-chain y la capa de pago son rails distintos, y confundirlos deja un hueco.

Cómo mantenerse en la frontera

Pasos concretos, en orden.

// Paso 1

Publicar una receta de firma ERC-7739 para x402

La pieza faltante es del lado del cliente. Enviar un helper pequeño en nuestro SDK comprador que, cuando el pagador sea una smart account ERC-7579 con Smart Sessions, envuelva el typed data TransferWithAuthorization de EIP-3009 en el envelope TypedDataSign de ERC-7739 con el permissionId de la sesión al frente y el domain separator de USDC como APP_DOMAIN_SEPARATOR. Documentar que la sesión debe habilitarse con TransferWithAuthorization(...) en allowedERC7739Content y al menos una policy ERC-1271 instalada. Esta es la diferencia entre "los agentes smart-account pueden pagarnos" y "reciben un rechazo que no pueden depurar."

// Paso 2

Enviar una spending policy consciente de x402

El hueco del ERC20SpendingLimitPolicy es corregible en la fuente: una action policy que reconozca transferWithAuthorization y receiveWithAuthorization, decodifique el monto autorizado, y lo mida contra un tope por sesión. Solo ayudaría en el caso donde una cuenta ejecuta el settlement ella misma, pero cierra el hueco de "el presupuesto no puede ver x402" y es una contribución upstream limpia a un módulo muy usado.

// Paso 3

Nunca inferir un tope desde un módulo de cuenta

Volverlo una regla dura en nuestra lógica de billing: el tope de gasto autoritativo es el que aplicamos sobre el pago, no uno que asumimos desde la arquitectura de wallet del pagador. Mantener el balance por agente, el chequeo de monto de x402 y la ventana de validez de EIP-3009 como el techo real, exactamente como en nuestra auditoría de spend controls.

// Paso 4

Seguir al sucesor basado en intents

Rhinestone publicó smart-sessions-v2 (SmartSessionEmissary) el 2026-09-27, extendiendo las session keys para trabajar con The Compact de Uniswap en operaciones cross-chain basadas en intents. Los intents son hacia donde va el mundo de account abstraction, y el settlement cross-chain está de lleno en nuestro carril. Seguir ese repo; la superficie de firma y policy que introduce definirá la próxima versión de esta frontera.

Smart Sessions es una capa de permisos fuerte y bien auditada. No es, de fábrica, un limitador de gasto de x402 —y saber exactamente por qué es lo que nos permite construir la pieza que hace que ambos encajen—. La economía de agentes va a correr sobre smart accounts. Va a pagar con firmas off-chain. El trabajo está en la costura entre ambas.

Construye agentes que paguen con límites reales

LLM4Agents aplica el tope de gasto donde cuenta: sobre el pago. Gateway compatible con OpenAI, settlement x402 y EIP-3009, balances por agente.

Registrar un agente