← Blog
25 de agosto, 2026 · 12 min

Spend Permissions: el presupuesto onchain para wallets de agentes

Nuestra auditoría de los spend controls de x402 terminó en un hueco: el cap del lado del cliente es solo por pago, sin contador acumulado ni presupuesto de sesión en ninguna parte del protocolo. Esta semana auditamos la capa que sí lleva un contador — onchain, por período, aplicado por consenso y no por un default de SDK.

Esa capa es el SpendPermissionManager de Coinbase, el contrato detrás de la feature "Spend Permissions" de lo que hoy se llama Base Account (antes Coinbase Smart Wallet). Es lo más cercano en producción a la pieza faltante que señalamos en la auditoría de spend controls: un presupuesto que sobrevive entre pagos porque la propia cadena hace la contabilidad. Como en cada auditoría de esta serie, trabajamos desde fuentes primarias: clonamos el repo, leímos las 1.078 líneas de Solidity, verificamos el bytecode desplegado en cada cadena que el README declara y contamos cada evento que el contrato emitió en los últimos siete días.

El objeto: nueve campos, una firma

Un spend permission es un struct EIP-712, firmado una vez por la smart account del usuario. Todo el modelo cabe en nueve campos:

struct SpendPermission {
    address account;    // smart account cuyos tokens se pueden gastar
    address spender;    // entidad autorizada a gastarlos
    address token;      // token nativo ERC-7528 o ERC-20
    uint160 allowance;  // gasto máximo por período
    uint48  period;     // intervalo de reset, en segundos
    uint48  start;      // válido desde (inclusivo)
    uint48  end;        // válido hasta (exclusivo)
    uint256 salt;       // diferencia permisos por lo demás idénticos
    bytes   extraData;  // payload opaco para el spender
}

La semántica es "10 USDC por mes a este spender hasta esta fecha". No es un call scope ni una session key con poderes arbitrarios — un token, un monto, un reloj. El README es explícito en que esa estrechez es el punto: el diseño "does not enable apps to make arbitrary external calls from user accounts", y los tokens ERC-721 se rechazan activamente al aprobar (ERC721TokenNotSupported). El dominio EIP-712 es "Spend Permission Manager", versión 1, y las aprobaciones aceptan firmas ERC-6492, así que un permiso puede firmarse antes de que la smart account exista — el validator la despliega como efecto colateral la primera vez que se usa el permiso.

La contabilidad: un contador que el cliente no puede olvidar

Todo lo que nuestra auditoría de x402 encontró ausente del lado del cliente ocurre aquí en unas treinta líneas. Cada permiso se hashea a un slot de storage que guarda un struct PeriodSpend: inicio del período, fin del período, gasto acumulado. En cada llamada a spend(), _useSpendPermission carga el período actual, suma el nuevo valor y revierte con ExceededSpendPermission(value, allowance) si el total cruza la allowance. Cuando el reloj pasa un límite de período, el contador vuelve a cero y la misma allowance queda disponible otra vez.

Dos propiedades de ese diseño importan para operadores de agentes. Primera: los períodos son un calendario fijo, no una ventana móvil. Los límites caen en start + n * period, determinísticamente, para siempre. El doc de accounting lo detalla con ejemplos. La consecuencia: un spender puede drenar la allowance completa en el último bloque del período n y otra vez en el primer bloque del período n+1 — hasta 2x el presupuesto por período en segundos, una vez por frontera. Es intencional y fácil de razonar, pero no es un rate limit.

Segunda: no hay ningún cap por transacción. La allowance acota el período, y una sola llamada puede consumirla entera. Las dos capas son espejos exactos: los spend controls del cliente x402 capan cada pago y olvidan el pasado; el permiso onchain recuerda el pasado y no le importa ningún pago individual. Ninguna referencia a la otra.

La revocación es simétrica e inmediata: la cuenta puede llamar revoke(), y el spender puede revokeAsSpender() para abandonar su propio permiso. Hay además un dispositivo anti-frontrunning que no esperábamos: approveWithRevoke() reemplaza atómicamente un permiso por otro, pero solo si el estado contable del permiso revocado coincide con un expectedLastUpdatedPeriod que la cuenta pasa como argumento — así el spender no puede ganarle la carrera al reemplazo con un último drenaje.

El acople con la wallet: un owner, no un estándar de módulos

¿Cómo mueve fondos de wallets ajenas un contrato singleton? Siendo owner de ellas. En vez de lanzar una Smart Wallet V2, Coinbase aprovechó el sistema modular de owners de la V1: el singleton SpendPermissionManager se agrega como owner de la cuenta del usuario, y _execute llama directo a CoinbaseSmartWallet.execute. La ruta de gasto nunca toca el EntryPoint de ERC-4337 — el README nota que eso evita que los paymasters gasten tokens del usuario en gas.

El costo del atajo es acople. Esto no es una implementación de ERC-7715 ni un módulo ERC-7579: funciona porque la wallet de Coinbase lo acepta como owner. El repo lo sabe: entre los PRs abiertos hay un borrador de ERC7579SpendPermissionManager (#69, abierto desde marzo de 2025) y un "EOA-supported SpendPermissionManager" (#66, febrero de 2025). Ninguno mergeado. El propio ERC-7715 — la vía interoperable para que una app pida permisos a cualquier wallet, con wallet_requestExecutionPermissions — sigue en estado Draft desde mayo de 2024. El estándar interoperable es un borrador; el singleton propietario-pero-abierto es lo que está desplegado y auditado.

Detalles que solo muestra el código

Tres mecanismos en las 776 líneas de SpendPermissionManager.sol vale la pena conocer aunque nunca lo leas. El primero es cómo se mueven realmente los fondos. spend() transfiere tokens de la cuenta al spender — no a un comercio. Para ERC-20s, el manager instruye a la cuenta a hacer approve del valor exacto al propio manager y luego llama safeTransferFrom, de modo que ninguna allowance de token queda viva tras la llamada. Para token nativo es más raro: la cuenta envía ETH al manager, protegido por una variable en transient storage con el monto exacto esperado — el receive() revierte ante cualquier otro valor en cualquier otro momento — y el manager lo reenvía al spender. Ajustado, pero significa que cada gasto son dos saltos, y el spender siempre es custodio de fondos recién extraídos hasta que hace algo con ellos.

El segundo es la ruta MagicSpend. spendWithWithdraw() permite al spender fondear atómicamente la cuenta desde el singleton MagicSpend de Coinbase y gastar en la misma transacción — liquidez just-in-time para cuentas que no tienen el token. El binding es cuidadoso: el asset del withdraw request debe coincidir con el token del permiso, el monto no puede exceder el valor del gasto, y los 128 bits bajos del nonce del withdraw deben igualar los 128 bits bajos del hash del permiso, así una firma de withdraw no puede reutilizarse contra otro permiso.

El tercero es el batching. approveBatchWithSignature aprueba muchos permisos — misma cuenta, misma ventana de período, distintos spenders, tokens y allowances — bajo una sola firma EIP-712. Para operadores de agentes esta es la primitiva de flota: una firma humana provisiona presupuestos por agente y por token para un roster completo. La construcción del hash del batch (SpendPermissionBatch envolviendo PermissionDetails[]) es verificable onchain, y cada permiso del batch sigue siendo revocable individualmente después.

Lo que verificamos onchain

El README declara una dirección — 0xf85210B21cC50302F477BA56686d2019dC9b67Ad — en ocho mainnets: Base, Ethereum, Optimism, Arbitrum, Polygon, Avalanche, BNB Smart Chain y Zora. Llamamos eth_getCode en las ocho el 25-ago-2026. Las ocho devuelven el mismo runtime code de 12.610 bytes con los mismos dos immutables embebidos: el PublicERC6492Validator en 0xcfCE48…293D y el singleton MagicSpend en 0x011A61…aB92. Comparando byte a byte los despliegues de Base y Ethereum, difieren exactamente 34 bytes, en dos regiones: los 32 bytes del domain separator EIP-712 cacheado y los 2 bytes del chain id cacheado (0x2105 vs 0x01). Todo lo demás es byte-idéntico.

Después contamos uso. Barriendo los 302.400 bloques que terminan en el bloque 50.429.665 de Base — siete días, aproximadamente del 18 al 25 de agosto — el contrato emitió 547 eventos SpendPermissionUsed, 368 SpendPermissionApproved y 13 SpendPermissionRevoked, sobre 351 hashes de permiso distintos, 231 cuentas distintas y 331 direcciones de spender distintas. USDC domina: 492 de los 547 gastos, por un total de 15.236,45 USDC — un promedio de unos $31 por gasto. El resto son 55 gastos de un token de cola larga llamado BEATS. Nótese la relación spenders/permisos: 331 spenders sobre 351 permisos significa que las direcciones de spender son esencialmente de un solo propósito — una dirección fresca por permiso otorgado, que es exactamente la forma de los spenders con scope de agente o de sesión.

Las otras cadenas están más quietas. Barridos completos de siete días en Optimism, Avalanche y Zora devolvieron cero eventos, igual que una muestra de los últimos 100.000 bloques de Arbitrum. Ethereum, Polygon y BSC no pudimos barrerlos — todos los endpoints RPC públicos que probamos rechazan eth_getLogs a ese rango — así que los reportamos como no medidos, no como cero. Pero la forma es clara: este es un sistema nativo de Base con una huella multichain esperando demanda.

El hallazgo central — las dos capas limitadoras de gasto del stack de pagos de agentes no componen. Los SDKs cliente de x402 capan cada pago en $1 por defecto pero no llevan total acumulado; SpendPermissionManager lleva un total acumulado por período aplicado por consenso pero no tiene granularidad por pago. En todo el monorepo de x402 (HEAD bde6fe5, 25-ago-2026), la dirección del manager aparece en exactamente cinco archivos — todos bundles generados del template del paywall de navegador, que embeben el Base Account SDK para el checkout humano. El string SpendPermissionManager aparece cero veces. Ningún scheme, cliente ni facilitator en la ruta agente del protocolo sabe que el presupuesto onchain existe.

Las costuras de agentes que sí existen

El stack de agentes de la propia Coinbase, en cambio, ya está cableado. Los docs de CDP listan "Agentic payments — control your agent's spending limits for autonomous operations" como caso de uso de primera clase, junto a suscripciones y trading algorítmico, con métodos server-side createSpendPermission / listSpendPermissions / useSpendPermission / revokeSpendPermission. (Curiosamente, esos docs listan seis mainnets, omitiendo Zora y BSC de las ocho del README.)

Y AgentKit — el framework de Coinbase para darles wallets a LLMs — expone el loop completo como tools que el modelo puede llamar: list_spend_permissions y use_spend_permission sobre wallets CDP, más list_base_account_spend_permissions, spend_from_base_account_permission y revoke_base_account_spend_permission para Base Accounts. La implementación tiene decisiones que conviene conocer antes de entregárselas a un modelo: use_spend_permission "automatically finds the latest valid spend permission" en vez de recibir uno explícito, y el amount del tool de Base Account es opcional — "if not provided, will withdraw the full remaining allowance". Un parámetro de tool que drena por defecto es exactamente el tipo de cosa que la allowance onchain existe para hacer sobrevivible; aun así vale notar que el default es drenar.

La distribución tampoco es el cuello de botella. El paquete npm que lleva el SDK cliente, @base-org/account, registró 6,70 millones de descargas en los 30 días al 23 de agosto; @coinbase/cdp-sdk, 3,41 millones; @coinbase/agentkit, 42.908. Los rieles están publicados e instalados. Los 547 gastos semanales dicen que el uso autónomo real es temprano — cientos de agentes, no millones.

Un repo que entregó y se detuvo

La historia del repo cuenta un relato comprimido. Creado en octubre de 2024 (primer commit en junio de 2024), 415 commits, licencia MIT, tres auditorías de Cantina en tres meses (octubre, noviembre, diciembre de 2024) — y el manager quedó terminado. El HEAD al momento de esta auditoría es del 24-mar-2026. La actividad de 2026 es toda sobre un segundo contrato, SpendRouter: un singleton de 274 líneas que decodifica un par (executor, recipient) del extraData del permiso y reenvía los fondos gastados directamente a un comercio, cerrando el hueco del diseño base donde spend() solo puede mover fondos al propio spender. SpendRouter recibió dos auditorías de Cantina en marzo de 2026 (el 18 y el 21) — y el README todavía lista su dirección de despliegue como "TBD" cinco meses después. Auditado, sin publicar.

Mientras tanto hay 13 ítems abiertos — 3 issues, 10 PRs — incluyendo el trabajo de interoperabilidad (ERC-7579, soporte EOA, exploraciones de Permit3) y un issue que propone composición con los prepared-transaction envelopes de ERC-8265. El patrón rima con lo que encontramos en la auditoría de los contratos ERC-8004: el núcleo es chico, auditado y vivo; los bordes del ecosistema son donde el momentum espera.

Qué significa para LLM4Agents

Para un gateway cuyos agentes pagan por llamada en stablecoins, los spend permissions resuelven el problema que vive una capa arriba de x402: no "cómo paga un agente este request" sino "cuánto puede gastar este agente antes de que un humano vuelva a mirar". Hoy LLM4Agents responde eso con balances depositados — el agente no puede gastar lo que no se depositó. Un spend permission invierte el flujo: los fondos del operador se quedan en su propia cuenta, y el agente (o el gateway actuando como spender) extrae hasta N USDC por período según acumula uso. Es una historia de tesorería materialmente mejor para operadores con flotas: sin depósitos ociosos repartidos entre proveedores, un permiso revocable por agente, y un tope mensual aplicado por consenso que ninguna config de SDK comprometida puede levantar — exactamente el modo de falla que demostramos en la auditoría de spend controls, donde activar allowedAssets quitaba el techo en silencio.

El lado amenaza es igual de concreto. Base Account más wallets CDP más AgentKit más el facilitator de x402 es un loop verticalmente integrado: Coinbase custodia la wallet, otorga el presupuesto, ejecuta el gasto y liquida el pago. Un gateway que solo acepta depósitos prefondeados se ve rígido al lado de "dale a tu agente $50/mes, revoca cuando quieras". Y los números medidos — 547 gastos por semana, $31 de promedio — dicen que el mercado de presupuestos onchain para agentes es lo bastante chico como para que quien integre temprano ayude a definir las convenciones.

Cómo mantenerse en la frontera

Pasos concretos, en orden. Primero, aceptar spend permissions como riel de fondeo: que un operador otorgue a la dirección de tesorería de LLM4Agents una allowance periódica de USDC en Base y que el gateway extraiga depósitos a medida que los balances bajan, con getCurrentPeriod expuesto en el dashboard para que el presupuesto onchain restante se vea junto al metering propio del gateway. El contrato es un singleton en una dirección conocida en ocho cadenas; la superficie de integración son tres funciones.

Segundo, componer los dos caps explícitamente en la política de agentes: techos por pago con los spend controls de x402, techos por período con la allowance onchain, y acumulación del lado del gateway entre ambos — tres capas, tres dominios de falla distintos. Documentarlo como la arquitectura de presupuesto de referencia para agentes autónomos, porque nadie más lo ha hecho: las dos capas salen de la misma empresa y hoy no se mencionan entre sí.

Tercero, vigilar dos artefactos. El despliegue de SpendRouter haría práctico el settlement directo a comercios desde cuentas de usuario — relevante para si futuros schemes de x402 pueden liquidar desde un permiso en vez de una hot wallet de agente. Y que ERC-7715 salga de Draft haría los pedidos de permiso agnósticos de wallet, que es cuando un gateway debería soportar presupuestos otorgados desde wallets no-Coinbase. Ninguno exige construir hoy; ambos merecen un tripwire.

El resumen en una línea de esta auditoría: la aplicación de presupuestos en la economía de agentes está hoy partida entre un default de SDK que olvida y un contrato que recuerda — y la costura de protocolo entre ambos sigue sin coser. La capa que cierre esa costura va a ser dueña de una pieza muy útil del stack.

Dale a tus agentes un presupuesto, no un cheque en blanco

LLM4Agents mide cada llamada contra balances que tú controlas — 345+ modelos detrás de un gateway OpenAI-compatible.

Registra tu agente