← Blog
22 de septiembre, 2026 · 13 min

El interruptor de congelación bajo x402: auditoría del blacklist de USDC

Un agente autónomo que paga por llamada mantiene saldo en un activo que su emisor puede apagar. Contamos cuántas veces se acciona ese interruptor y rastreamos qué hace x402 cuando ocurre.

El stack de pagos de agentes dedica casi toda su atención a las firmas. Qué curva, qué domain separator, qué tipo de wallet, qué facilitator. Ese trabajo está hecho y en general es correcto. Debajo hay una pregunta más simple que casi nada del stack modela: el token tiene un administrador, y ese administrador puede dejar a una dirección sin poder enviar ni recibir, de forma permanente, en una sola transacción.

No es hipotético. Es una función del contrato, emite un evento, y el log de eventos es el registro público de cada vez que se usó. Así que leímos el contrato, contamos los eventos en Ethereum y Base, valuamos los saldos inmovilizados, y después entramos al código de x402 para ver qué reporta un facilitator cuando el payer queda congelado a mitad del flujo.

Todo lo que sigue se midió el 2026-09-22 contra mainnet.

El interruptor, en Solidity

USDC es un proxy sobre la implementación stablecoin-evm de Circle. El contrato relevante es Blacklistable: un rol blacklister, un par blacklist(address) / unBlacklist(address) protegido por onlyBlacklister, una vista pública isBlacklisted(address) y dos eventos, Blacklisted y UnBlacklisted.

La aplicación es un modifier. En FiatTokenV2_2, la versión desplegada en todas las cadenas importantes, los entry points de EIP-3009 lo llevan explícito:

function transferWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    bytes memory signature
) external virtual whenNotPaused notBlacklisted(from) notBlacklisted(to) {

// Blacklistable.sol
modifier notBlacklisted(address _account) {
    require(
        !_isBlacklisted(_account),
        "Blacklistable: account is blacklisted"
    );
    _;
}

Tres detalles importan para pagos máquina a máquina.

Primero, el gate cubre from y to —payer y payee— pero no msg.sender. Un facilitator que relaya y que está él mismo blacklisteado puede seguir enviando transferWithAuthorization por cuenta de otros. Lo que no puede es mover su propio USDC, porque transfer y transferFrom en FiatTokenV1 sí verifican msg.sender. El rol de relayer sobrevive a un listado; el rol de tesorería no.

Segundo, en V2_2 el flag de blacklist no es un mapping aparte. Es el bit alto del slot empaquetado de balance: _setBlacklistState hace un OR de 1 << 255 sobre balanceAndBlacklistStates[account], y _setBalance revierte con "FiatTokenV2_2: Account is blacklisted" si ese bit está puesto. Saldo y censura viven en la misma palabra de storage. El saldo sigue siendo legible —balanceOf enmascara el bit—, y por eso los montos inmovilizados de abajo se pueden contar.

Tercero, initializeV2_2 blacklistea address(this). Lo confirmamos en Base: isBlacklisted(0x8335…2913) sobre el propio contrato de USDC devuelve true. El token se niega a recibirse a sí mismo.

Una sonda en vivo sobre Base

El orden de los modifiers decide qué ve el agente. Tomamos una dirección actualmente listada en Base y llamamos a transferWithAuthorization con una firma deliberadamente inválida; después repetimos con una dirección limpia, ambas vía eth_call contra Base mainnet:

// from = 0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b (listada en Base)
execution reverted: Blacklistable: account is blacklisted

// from = dirección nunca listada, misma firma basura
execution reverted: ECRecover: invalid signature

El chequeo de blacklist corre antes de la recuperación de la firma. Es buena noticia para quien quiera pre-filtrar: un solo eth_call a isBlacklisted —o una simulación— dice que el payer es inservible antes de gastar una firma, un nonce o gas. También explica por qué el fallo pasa desapercibido: el revert nunca menciona firma, monto ni nonce, así que un handler genérico lo archiva como "la transferencia revirtió".

El censo: 833 direcciones en Ethereum, 639 todavía listadas

Bajamos el historial completo de Blacklisted y UnBlacklisted del USDC de Ethereum (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) desde antes del deploy hasta el head, reprodujimos los eventos en orden de bloque para obtener el estado actual y leímos cada saldo en vivo.

// USDC Ethereum, historial completo al 2026-09-22

887 listados, 224 reversiones, 639 direcciones congeladas hoy

887 eventos Blacklisted sobre 833 direcciones distintas. Primer listado el 2020-06-16 (bloque 10.274.701), el más reciente el 2026-09-15. 224 eventos UnBlacklisted sobre 194 direcciones. Neto actualmente listado: 639.

Saldo en poder de esas 639 direcciones: 120.734.323,95 USDC. Solo 147 tienen algo; 492 están congeladas en cero. Las dos mayores concentran 30,1M y 27,7M.

Lo interesante es la cadencia. Listados por año: 1 en 2020, 24 en 2021, 126 en 2022, 60 en 2023, 54 en 2024, 215 en 2025 y 407 en 2026 hasta el 15 de septiembre. Sea cual sea la política, su tasa de aplicación se duplicó año contra año desde 2024, y 2026 ya triplica a 2025.

También es a ráfagas, no continuo. Los cinco días más grandes en Ethereum son 2026-07-01 (131 direcciones), 2026-03-23 (80), 2025-11-04 (74), 2022-11-10 (62) y 2026-09-09 (54). El lote del 2026-03-23 cayó dentro de una ventana de 46 minutos. Por mes, 2026 se lee: 11, 1, 109, 19, 18, 3, 160, 31, 55. Quien monitorea esto observa un proceso inactivo durante semanas que de golpe lista cien direcciones en una tarde.

Las reversiones existen pero están concentradas: 125 de las 224 ocurrieron en 2025 y 88 en 2026, incluidas 56 solo en mayo de 2026. Quedar listado no siempre es terminal, pero la reversión es un acto administrativo en los tiempos de otro, no algo que el payer pueda gatillar.

Base tiene una lista más chica, más nueva y más activa

Base es donde liquida la mayor parte del tráfico x402, así que corrimos el mismo censo sobre 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913.

565 eventos Blacklisted, 562 direcciones distintas, 93 reversiones, 469 listadas hoy, con 2.371.161,96 USDC retenidos — y solo 23 de las 469 tienen saldo distinto de cero. El primer día de la lista es el 2023-08-18, el día en que el USDC de Base empezó a emitir eventos: 141 direcciones en un solo lote, es decir la lista de Ethereum sembrada en una cadena nueva y no 141 decisiones independientes.

Desde entonces: 27 listados en 2024, 125 en 2025 y 254 en 2026. Los días más cargados son 2026-07-01 (131), 2026-09-09 (52) y 2026-08-24 (22). Base carga cerca del 2% del valor congelado de Ethereum pero una cantidad de direcciones comparable, consistente con que Base es donde viven las direcciones chicas, rápidas y desechables. Que es exactamente lo que es una wallet de agente.

La lista es por cadena, y solo casi sincronizada

La política de Circle dice que puede bloquear direcciones "en cada blockchain en la que se emite Circle Stablecoin". Los contratos, sin embargo, mantienen estado independiente, y los logs muestran la diferencia entre capacidad y práctica.

De las direcciones que vimos listadas, 545 aparecen en Ethereum y Base, 288 solo en Ethereum y 17 solo en Base. El conjunto exclusivo de Ethereum no es únicamente herencia: 253 de esas 288 se listaron después de que existiera la lista de Base, incluidas 152 durante 2026. El ejemplo más claro es el 2026-03-23: 80 direcciones listadas en Ethereum, cero en Base ese día.

Cuando la propagación ocurre, es rápida. Sobre las 544 direcciones compartidas con timestamps comparables, la mediana entre el listado en Ethereum y el listado en Base es de 190 segundos, y 409 de ellas quedaron listadas en ambas cadenas dentro de la misma hora. El 2026-07-01 las mismas 131 direcciones se listaron en las dos cadenas dentro de ventanas de cuatro horas superpuestas.

Lectura operativa — tres minutos es el mismo orden de magnitud que una ventana de autorización de x402. El resource server de x402 usa maxTimeoutSeconds 300 por defecto (los ejemplos de la spec exact EVM usan 60). Un payer puede estar limpio en verify y listado en settle dentro del mismo pago.

También leímos los roles administrativos. Cada cadena tiene su propio blacklister, y las seis que revisamos son EOAs sin código: Ethereum 0x0a06…78f9, Base 0x1f2e…e277, Polygon 0xe99c…95b6, Arbitrum 0xac5b…1329, Avalanche 0x00fe…d572, Celo 0x4a63…ca12. Ninguno de los seis contratos está en pausa, y rescuer() es la dirección cero en todos.

Qué promete Circle por escrito

El documento que gobierna esto es la Circle Stablecoin Access Denial Policy, referenciada desde la página de USDC risk factors. Son dos páginas y vale leerlas completas. La estructura es un default más excepciones: Circle "no denegará acceso a direcciones individuales, salvo en circunstancias que se ajusten estrictamente" a tres excepciones — una amenaza a la integridad de la red (por ejemplo una clave de minter comprometida), cumplimiento de "una ley, regulación u orden legal de una autoridad reconocida de EE.UU. o Francia", y pedidos urgentes de law enforcement o de sanciones a la espera de una orden judicial válida.

Una nota al pie acota bastante el foro: "los tribunales estadounidenses de jurisdicción competente están en Delaware o Massachusetts". La reversión solo procede "ante confirmación formal" de la misma autoridad de que la orden fue levantada. No hay requisito de notificación al titular de la dirección, ni plazo declarado, ni vía de apelación en el documento.

La sección 5 es un compromiso: "Circle reportará públicamente de forma regular el monto más actualizado de tokens Circle Stablecoin con acceso denegado". No pudimos encontrar esa cifra publicada en la página de transparency de Circle, que cubre reservas y attestations. El número de este post —unos 123,1M USDC inmovilizados entre Ethereum y Base— es nuestro propio conteo del log de eventos, y cualquiera puede reproducirlo con dos llamadas a getLogs y un batch de lecturas balanceOf.

Dónde x402 está ciego

Clonamos x402-foundation/x402 en HEAD 279f12c (2026-09-22) y seguimos el camino exact EVM de punta a punta.

La buena noticia: verifyEIP3009 simula por defecto. Un payer listado falla la verificación en vez de descubrirse en el settlement. La mala noticia es cómo se llama ese fallo.

Cuando la simulación revierte, el facilitator llama a diagnoseEip3009SimulationFailure, que hace multicall de cuatro lecturas —balanceOf, name, version, authorizationState— y las mapea a razones específicas: nonce ya usado, mismatch de nombre del token, mismatch de versión, saldo insuficiente, EIP-3009 no soportado. No hay ninguna lectura de isBlacklisted en esa lista. Un payer congelado pasa todos los diagnósticos (el saldo está intacto y es legible) y cae al genérico invalid_exact_evm_transaction_simulation_failed.

En el settlement el mismo hueco aparece en forma de string. parseEip3009TransferError matchea cinco expresiones regulares —autorización expirada, aún no válida, nonce usado, saldo insuficiente, firma inválida— y devuelve invalid_exact_evm_transaction_failed para todo lo demás. "Blacklistable: account is blacklisted" no matchea ninguna. Y simulateInSettle viene en false por defecto, así que un payer listado entre verify y settle quema el gas del facilitator en una transacción que revierte y reporta un fallo genérico.

// lo que ve el agente ante un payer congelado de forma permanente
{ "isValid": false,
  "invalidReason": "invalid_exact_evm_transaction_simulation_failed",
  "payer": "0x…" }

// indistinguible de: hipo del RPC, token en pausa, nodo malo,
// reorg transitorio — todos vale reintentarlos. Este no.

Ahí está el problema entero en un campo. El módulo de errores de exact EVM exporta 60 códigos, 19 de ellos variantes de Permit2, y ninguno significa "este payer no va a poder pagar nunca". Un agente con retry y backoff va a reintentar contra una wallet que ya no va a funcionar, y un gateway que degrada a un payer tras N fallos transitorios va a tratar una condición permanente como inestabilidad.

La spec también calla. scheme_exact_evm.md tiene 2.221 palabras y cero ocurrencias de blacklist, freeze o frozen. El scheme exact de TON rechaza explícitamente cuentas frozen y la spec de batch-settlement en SVM maneja cuentas de settlement congeladas; el camino EVM, que carga casi todo el volumen vivo de x402, no menciona el congelamiento por parte del emisor como modo de fallo.

Bridged USDC: el modo de fallo inverso

La otra cara del interruptor es dónde no existe. Los Third-Party Bridged USDC Terms de Circle lo dicen sin vueltas: "Circle no controla el contrato de Bridged USDC en las Supported L2 Networks y Circle carece de la capacidad de bloquear ciertas direcciones o congelar Bridged USDC en caso de que tus fondos sean robados".

La tabla de default assets EVM de x402 mezcla los dos mundos. Lista USDC nativo en Ethereum, Base, Polygon, Arbitrum, Avalanche y Celo, junto a entradas etiquetadas "Bridged USDC", "USDC.e", USDT0 y varios dólares específicos de cada cadena. Son activos distintos con administradores distintos y modos de fallo distintos: sobre USDC nativo el emisor puede congelarte; sobre bridged USDC nadie puede congelar a nadie, incluso cuando te roban los fondos. Ninguna de esas propiedades se expresa en el metadata de activos de x402, que lleva name, version, decimals y symbol. Cubrimos el blocklist análogo en el camino EIP-3009 de USDT0 en la auditoría del WDK de Tether; la mecánica cambia según el emisor, y nada en el protocolo le dice al comprador contra cuál está por firmar.

Qué significa para LLM4Agents

Liquidamos inferencia en USDC sobre EIP-3009, mayormente en Base. Eso implica tres exposiciones distintas, y no son igual de graves.

La primera es el payer. La wallet de un agente queda listada y toda solicitud posterior falla. Es el caso leve: la pérdida está acotada al saldo del propio agente, y la tarea de nuestro gateway es reportarlo correctamente y dejar de cobrar reintentos contra él. Hoy, con la taxonomía de errores del facilitator de stock, no podemos distinguirlo de un RPC malo.

La segunda es el payee: nuestra propia dirección payTo. El modifier controla to igual que from. Un listado sobre una dirección receptora no degrada el servicio: detiene todo el settlement entrante en esa cadena de golpe, para todos los agentes, con autorizaciones ya firmadas en vuelo que dejan de poder liquidarse. Esta es la que merece ingeniería: una única dirección receptora es un punto único de falla que un tercero puede accionar sin aviso.

La tercera es el float. 120,7M USDC quedaron inmovilizados en Ethereum, y la concentración es extrema: 147 de 639 direcciones listadas tienen algo, y dos de ellas concentran el 48% del total. El valor estacionado en una dirección es toda la exposición; el valor que la atraviesa en segundos es casi ninguna. Nuestro diseño reserve-proxy-settle ya mantiene saldos de vida corta, que es la postura correcta acá por razones que nada tienen que ver con eficiencia de capital.

La dirección regulatoria refuerza las tres. Como escribimos en el análisis del GENIUS Act, la trayectoria de compliance para stablecoins de pago apunta a que los emisores tengan —y usen— la capacidad técnica de actuar ante órdenes legales. La tasa de listados de 2026 es cómo se ve eso en el log de eventos.

Cómo mantenerse en la frontera

Cinco pasos concretos, en el orden en que los haríamos.

1. Monitorear nuestras propias direcciones. Un watcher suscrito a Blacklisted en cada cadena donde tengamos un payTo o una dirección de tesorería, con alerta dentro del mismo bloque. El evento está indexado por dirección, así que el filtro es exacto y barato. Es un día de trabajo y convierte una caída silenciosa en una alerta.

2. Hacer reemplazable al payee. Direcciones receptoras por cadena, rotadas con cierta periodicidad, con los payment requirements generados desde un registro y no desde una constante. Si un payTo queda listado, la próxima respuesta 402 anuncia otro. La rotación además evita que una sola dirección receptora acumule historial sobre el que valga la pena actuar.

3. Pre-filtrar y clasificar. Agregar una lectura isBlacklisted al multicall de diagnóstico del facilitator —es una llamada extra en un batch que ya hace cuatro— y un código de razón propio, para que un payer congelado se reporte como permanente y no se reintente. Vamos a subir esto upstream a x402 como tres cambios: la rama de diagnóstico, un patrón "Blacklistable: account is blacklisted" en parseEip3009TransferError, y una sección de modos de fallo en scheme_exact_evm.md. Es el parche más chico de este post y el de mayor radio, porque todo facilitator que corre la implementación de referencia hereda el punto ciego. Ver el post sobre la anatomía del facilitator para saber dónde encajan estos hooks.

4. Activar simulateInSettle sobre un umbral de valor. El default false es correcto para una llamada de inferencia de $0,001 y equivocado para un settlement en lote. Los 190 segundos de mediana de propagación que medimos son el ancho de la ventana que esto cierra.

5. Publicar el perfil administrativo del activo. Nuestro metadata estilo /supported debería decir, por activo, quién puede congelarlo y quién no: USDC nativo (el emisor puede congelar payer y payee), bridged USDC (nadie puede, ni siquiera ante robo), USDT0 (otro blocklist, otro camino). Un agente comprador que elige entre dos carriles debería poder leer eso desde los payment requirements y no desde un post de blog. Es el mismo argumento que hicimos sobre señales de confianza en el threat model de agentes: el poder administrativo no declarado sigue siendo poder.

Nada de esto hace desaparecer el interruptor. USDC es un pasivo regulado y la función de congelamiento es una característica de eso, no un bug. Lo que sí es corregible es la parte que nos toca: un agente nunca debería confundir un bloqueo permanente con un nodo inestable, y un gateway nunca debería permitir que una sola decisión de un tercero tumbe todos los pagos entrantes a la vez.

Paga por llamada, no acumules saldo

Saldos de vida corta, settlement por request y un gateway compatible con OpenAI que le dice a tu agente exactamente por qué falló un pago.

Registrar un agente