Wallets de agente de Crossmint: dónde el tope de gasto toca x402
Crossmint vende wallets de agente con "un tope de gasto, contrapartes permitidas y una ventana de tiempo", aplicados onchain. Rastreamos dónde se aplica ese tope cuando un agente paga un endpoint x402. En Stellar, el contrato open source de Crossmint revisa cada pago. En EVM, donde corre la guía x402 de Crossmint, los docs nunca lo dicen.
El stack de agentes de Crossmint tiene tres productos, pensados para usarse solos o juntos: Agent Cards, Agent Wallets y Agent Checkouts. La propuesta de la wallet es la que toda plataforma de agentes quiere hacer. El usuario es dueño de una wallet non-custodial. El agente recibe su propia key con límites. Los límites viven en el contrato, no en el prompt del agente. El usuario puede revocar la key cuando quiera.
Es el diseño correcto. Esta auditoría hace dos preguntas. ¿Cuánto de eso obtiene un desarrollador que sigue las guías de la propia Crossmint? ¿Y dónde se encuentra realmente un pago x402 con el límite?
Fuentes: la documentación de Crossmint tal como estaba el 9 de octubre de 2026; @crossmint/wallets-sdk 1.20.0 en el commit 4ab8408; la app de ejemplo M2M Payments en 42994f3; Crossmint/stellar-smart-account en bee3339; y el repositorio de x402 en 7f2b2f1. No abrimos una cuenta en Crossmint. Donde el comportamiento depende del backend cerrado de Crossmint, lo decimos.
La versión corta:
- La primitiva existe. Crossmint la llama scopes: un límite de gasto por token con intervalo de reinicio opcional, una allowlist de destinatarios y una expiración del signer.
- Las guías de agentes de Crossmint y su app de referencia M2M registran la key del agente sin scopes. Un signer sin scopes tiene gasto de tokens sin restricciones. El único tope del ejemplo es un default por llamada en el código de la aplicación, y el propio agente puede subirlo.
- En EVM, un pago x402 llega a la wallet como un chequeo ERC-1271 de solo lectura sobre un hash de 32 bytes. Ese camino no puede llevar un total acumulado. Crossmint no documenta si los scopes aplican a firmas de typed data.
- En Stellar, Soroban le entrega al contrato de la cuenta la llamada completa
transfer(from, to, amount), y la policy de Crossmint revisa token, destinatario, monto y una ventana acumulada. Es exactamente la llamada que hace un pago x402 en Stellar.
Tres productos, una promesa
El overview de agentes de Crossmint divide el trabajo en dos. Las tarjetas y las wallets son poder de gasto. Agent Checkouts y los protocolos x402 y MPP son adonde va ese poder de gasto. Un agente que compra en una tienda web usa una agent card dentro de un checkout. Un agente que paga una API por llamada, o que le paga a otro agente, usa una agent wallet sobre x402 o MPP.
El overview hace una sola promesa para ambos: "Card limits hold at Visa and Mastercard, wallet limits hold onchain". La página de Agent Wallets la repite. El usuario "adds the agent as a signer with a spend cap, allowed counterparties, and a time window", y "a transaction outside the delegation is rejected at the wallet level".
Según la página de arquitectura de Crossmint, la wallet EVM es una smart account ERC-4337 con módulos ERC-7579. En Solana es una program-derived address. En Stellar es un contrato Soroban. La misma página dice que "all permissions are enforced onchain and fully auditable".
Qué es realmente un scope
El tope, la lista de contrapartes y la ventana de tiempo corresponden a una sola función, documentada en Restrict a Signer with Scopes. El tipo del SDK es corto:
// @crossmint/wallets-sdk 1.20.0 — src/api/types.ts
export type TransferScope = {
type: "transfer";
tokenLocator: string;
recipients?: string[];
spendingLimit?: {
amount: string;
/** Positive integer representing the reset interval in seconds. */
interval?: number;
};
};
export type Scope = TransferScope;
El único tipo de scope es transfer. Cada scope cubre un token en una chain. El monto va en unidades de display: "10" significa 10 USDC. En EVM, Crossmint lee el decimals() del token para convertirlo "to the wei amount the policy contract expects". Sin interval, el tope es un presupuesto de por vida. Con él, el contador se reinicia la primera vez que el signer transfiere después de que pasó el intervalo. La expiración, expiresAt, va en el signer, no dentro del scope.
Dos reglas más marcan todo lo que sigue. Primero, un signer sin scopes "has unrestricted token spending". Segundo, los scopes se revisan "before the transaction is broadcast onchain", y una transferencia que supera el límite "is rejected at validation time". Fíjate en la palabra transaction.
La ruta de referencia sale sin scopes
Cuando los docs de wallets llegan a este paso, enlazan a la guía Authorize the Agent. Registra el server signer del agente así:
const { locator, signatureId } = await wallet.addSigner(
{ type: "server", secret: process.env.CROSSMINT_SIGNER_SECRET },
{ prepareOnly: true }
);
No hay argumento scopes. El quickstart de wallets describe la misma llamada y agrega: "From here on, the server signer can sign for the wallet without any user prompt". La página conceptual de signers explica qué significa. Un signer agregado después de crear la wallet "has the same transaction authority as the wallet's recovery methods by default".
La app de ejemplo M2M Payments de Crossmint, con último push el 6 de octubre, es la referencia para agentes que les pagan a máquinas. Registra el signer del agente con la misma llamada, también sin scopes. Su documento de arquitectura es claro sobre el resultado. La pantalla de aprobación le dice al usuario que el agente podrá "pay x402 and MPP services, transfer credits, send transactions, all from this wallet".
El ejemplo sí tiene un tope. Su pagador x402 rechaza un 402 que pida más que maxAmount, antes de firmar nada. El default del servidor es "1.00", definido por M2M_PAYMENTS_MAX_PAYMENT. Pero el handler de pagos usa body.maxAmount ?? ctx.maxPayment. El request del propio agente puede pasar cualquier valor positivo, y el mensaje de rechazo le dice al agente "Raise maxAmount if the user agrees". Los handlers de transferencia y de transacción cruda no tienen ningún tope. El skill incluido los cubre con un encabezado: "Transfers and transactions: only when the user asks".
La página How Agents Pay de la propia Crossmint señala este patrón exacto como el problema: "Limits enforced only in application code do not hold. They stop honest mistakes, not a compromised agent, a manipulated model, or a leaked credential".
El radio de impacto es amplio. Un server signer se deriva de un solo secreto y, según la referencia de tipos de signer, "the same secret produces one signer address across EVM chains within a project and environment". La guía de autorización advierte que "anyone who holds CROSSMINT_SIGNER_SECRET can sign on behalf of every wallet it has been authorized on". La revocación es por wallet. Una plataforma que autoriza un mismo signer de backend en la wallet de cada usuario tiene un secreto que firma por todas. En ese diseño, los scopes son el único límite por wallet. La ruta de referencia los deja afuera.
Agregar scopes después no es una edición
Un equipo que lanza siguiendo la guía y agrega límites más tarde choca con una pared. Los scopes "are immutable for the lifetime of the signer", y el SDK lo aplica sin hacer ruido. Si llamas a addSigner con scopes para un signer ya aprobado, registra un warning wallet.addSigner.scopesIgnored y devuelve el signer existente, sin scopes. Si el registro sigue pendiente, lanza un error y te dice que elimines el signer y lo registres de nuevo.
Así que agregar un tope significa eliminar el signer del agente, registrarlo otra vez con scopes y conseguir una aprobación nueva de cada usuario con su método de recuperación. El SDK tampoco puede fijar la expiración. Según la guía de scopes, "expiresAt and wallet-creation-time scope registration require the REST API".
La solución barata es ponerle scopes al signer en el primer registro. Para un agente que paga, el mínimo es un scope transfer de USDC con spendingLimit e interval, una lista recipients cuando se conocen las contrapartes, y un expiresAt.
Dónde toca un pago x402 a la wallet EVM
Supongamos ahora que el scope está puesto. ¿Alcanza a un pago x402? La guía x402 de Crossmint usa el cliente estándar @x402/core con el scheme exact de EVM. El signer que le pasa al cliente envuelve un solo método:
const x402Signer = {
address: evmWallet.address,
async signTypedData(typedData) {
const { signature } = await evmWallet.signTypedData({
...typedData,
chain: "base",
});
return signature;
},
};
Con USDC, ese typed data es un TransferWithAuthorization de EIP-3009. Sigamos la firma desde ahí.
Firma. En el SDK, signTypedData no firma localmente. Envía el typed data al endpoint Create Signature de Crossmint con type: "typed-data" y el locator del signer, espera la aprobación del signer y devuelve la firma de la wallet. La referencia de Create Signature tiene un flag isSmartWalletSignature que envuelve el resultado en ERC-6492. No tiene ningún campo ni error que mencione los scopes del signer.
Settlement. El facilitator nunca le pide a la wallet que actúe. El facilitator EVM de x402 quita el envoltorio ERC-6492 y llama a transferWithAuthorization de USDC con la firma como bytes. Lo verificamos en Base: el proxy de USDC apunta hoy a la implementación 0x2ce6311ddae708829bc0784c967b7d77d19fd779, cuyo bytecode contiene el selector 0xcf092995, la variante con firma en bytes. El SignatureChecker de Circle ve que el pagador tiene código y hace un staticcall a isValidSignature(bytes32, bytes) en la wallet.
agente ── typed data ──▶ API de Crossmint (Create Signature)
│ ¿chequeo de scope aquí? no documentado
▼
facilitator ── transferWithAuthorization(from, to, value, ..., bytes sig) ──▶ USDC
│
staticcall isValidSignature(bytes32 hash, bytes sig)
▼
wallet de Crossmint
De ahí salen tres hechos. La wallet recibe un hash de 32 bytes, no la transferencia. No puede conocer el monto ni el destinatario salvo que la firma misma lleve el typed data, que es para lo que existen las firmas anidadas de ERC-7739. Y no puede escribir estado. ERC-1271 dice que isValidSignature "should not be able to modify states", y un staticcall lo garantiza. Un tope acumulado con ventana de reinicio necesita un contador que suba con cada pago. Nada puede incrementarlo en este camino.
La guía de compatibilidad de wallets de x402 confirma el ruteo. Una smart account ERC-4337 desplegada es "Type B". Está soportada para exact con EIP-3009 y se verifica con el SignatureChecker del token.
Eso deja dos lugares donde un scope podría alcanzar a un pago x402 desde una wallet EVM de Crossmint. Uno es el servicio de firma de Crossmint, que podría negarse a producir un TransferWithAuthorization por encima del tope. Eso es aplicación en el backend de Crossmint, no onchain. El otro es un chequeo sin estado dentro del validador ERC-1271 de la wallet, que podría topar un pago individual pero nunca un total acumulado.
Los docs no describen ninguno de los dos. Describen un scope transfer que se revisa antes de que se haga broadcast de una transaction, y un pago x402 exact no es una transacción que envíe la wallet. Hoy no está documentado si la firma de typed data de un signer con scopes cuenta contra su límite. Eso requiere una prueba, y es el paso uno más abajo.
Chocamos con la misma frontera cuando auditamos Smart Sessions. Los módulos de la cuenta protegen el camino de ejecución, y EIP-3009 lo rodea. Crossmint usa el mismo modelo de cuenta, así que aplica la misma pregunta.
Dos trampas más silenciosas en el mismo camino
Instalación diferida. La guía para agregar signers de Crossmint dice que "a signer must be installed onchain before it can sign". Con deployImmediately: false, "the signer is installed on its first transaction". El endpoint REST usa false por defecto. El SDK usa true por defecto en EVM. Un agente que solo paga x402 nunca envía una transacción, así que no debería depender de la instalación diferida. La guía de compatibilidad de x402 describe la falla para wallets cuyo validador se instala tarde: "the deployed wallet can reject the inner signature and the on-chain transfer reverts".
Wallets contrafactuales. Una wallet que todavía no está desplegada paga con una firma ERC-6492. El facilitator de x402 la despliega solo si su factory está en eip6492AllowedFactories, y el default es una lista vacía que "denies all factory deployment calls". Una wallet nueva de Crossmint que le paga a un facilitator que no puso la factory de Crossmint en la allowlist falla en verify. Despliega la wallet primero.
En Stellar, el mismo tope se aplica onchain
Stellar muestra cómo se ve la promesa cuando el protocolo colabora. Crossmint publica como open source su contrato de cuenta para Stellar. Leímos el commit bee3339. No afirmamos nada sobre qué commit corre Crossmint en producción.
El modelo de autorización de Soroban difiere de ERC-1271 en las dos cosas que importan. El ejemplo complex-account de Stellar dice que __check_auth "will be called by the Soroban environment every time require_auth or require_auth_for_args is called for the address of the account contract". Su vector auth_context "contains all the contract calls that are being authorized". Y como solo el host puede llamarla, "it's safe to write to the account contract storage from __check_auth".
La TokenTransferPolicy de Crossmint usa ambas. Para cada llamada autorizada exige que el contrato sea el token de la policy, que la función sea transfer y que haya exactamente tres argumentos. Revisa el destinatario en args[1] contra la allowlist. Lee el monto de args[2], lo suma a un tracker persistente por signer, reinicia el tracker cuando pasó la ventana y rechaza cuando el total superaría el límite. También revisa la expiración. Un signer estándar sin policies no tiene restricciones, el mismo default que en EVM.
// contracts/smart-account/src/auth/policy/token_transfer.rs (bee3339)
if contract != policy.token { return None; }
if fn_name != symbol_short!("transfer") { return None; }
if args.len() != 3 { return None; }
// allowlist de destinatarios en args[1], monto desde args[2]
// luego: tracker.spent + amount <= limit, reinicio de ventana, record_spend()
Ahora compara con el scheme exact de x402 para Stellar. El pago es una transacción con exactamente un invokeHostFunction que llama a transfer(from, to, amount) sobre un token SEP-41. El cliente firma solo las entradas de autorización. El facilitator reconstruye la transacción con su propia cuenta como source, conserva las entradas de autorización y la envía. La spec prohíbe subInvocations más allá de la transferencia.
Cuando el token llama a from.require_auth() sobre una cuenta-contrato de Crossmint, Soroban llama a __check_auth con esa misma transfer en el contexto. La policy ve el token, el destinatario y el monto, y registra el gasto. El pago x402 y el scope se encuentran dentro del contrato.
Aquí también hay una brecha. Crossmint documenta x402 solo en EVM. El requisito de la guía es "a funded Crossmint EVM wallet on Base with USDC". La chain donde el tope demostrablemente alcanza a un pago x402 es la que Crossmint no documentó para x402.
Las tarjetas etiquetan el trade-off. Las wallets también deberían.
El lado de tarjetas del stack maneja el mismo trade-off a la vista. Una agent card es un order intent: un monto, una descripción, una expiración obligatoria y, opcionalmente, un comercio. A partir de él, el agente emite una credencial en un rail. En Visa Intelligent Commerce y Mastercard Agent Pay, la red sostiene el límite. El overview de agent cards indica quién aplica el límite en cada rail.
Crossmint también nombra el rail débil. Cuando una tarjeta no soporta ninguno de los dos programas de red, un order intent puede caer a una copia cifrada de la tarjeta guardada. La guía de creación lo dice sin rodeos: "Card networks enforce amount and merchant only for agentic-token rails". Con la tarjeta cifrada, la aplicación tiene que mantener la compra dentro de los límites. En producción ese rail requiere acceso por proyecto, "because minting from this rail gives your application the user's real card number".
Los docs de wallets no tienen una lista así, y los protocolos difieren. La guía de MPP de Crossmint usa el método tempo en modo push. La wallet envía una transacción onchain con sendTransaction, que es el camino que describe un scope transfer. El pagador MPP de la app M2M usa en cambio el método de cobro evm, que, según su propio comentario, "wants an EIP-3009 transfer authorization". Ese es el camino de la firma. Un producto, dos integraciones MPP publicadas, dos puntos de aplicación distintos. Nuestra auditoría de MPP cubre los modos de settlement del protocolo.
Qué significa para LLM4Agents
Nosotros estamos del otro lado de estos pagos. Nuestro camino walk-up vende inferencia detrás de una cotización x402, liquidada con una autorización EIP-3009 en USDC sobre Base. Una agent wallet de Crossmint es exactamente el comprador para el que existe ese camino. Se fondea con tarjeta, no requiere KYC para créditos de circuito cerrado en el diseño del ejemplo, y apunta a servicios para máquinas.
Los compradores con smart account son un caso de primera clase. Una wallet EVM de Crossmint paga desde un contrato, así que sus pagos pasan por la rama ERC-1271 de USDC. El USDC de Base soporta esa rama hoy. Una wallet contrafactual es distinta: salvo que su factory esté en la allowlist, el facilitator rechaza el despliegue ERC-6492.
Los topes del comprador actúan sobre nuestra cotización, no sobre nuestra factura. El tope por llamada por defecto del ejemplo es 1.00 crédito, comparado contra el monto del 402 antes de firmar. Nuestra cotización es un peor caso. En nuestra auditoría de tokenizer drift, una llamada con diez imágenes se cotizó en $1.13, mientras que la llamada en sí cuesta $0.08. Un comprador con tope de $1 la rechaza de entrada. Cada centavo que sobrecotizamos es un posible rechazo de un comprador con tope.
Un pago liquidado no dice nada sobre los límites del comprador. Como vendedores, vemos una firma válida y una transferencia. No podemos saber si el dueño le puso tope al agente, si se revisó un scope o si el agente subió su propio maxAmount. Lo mismo pasaba con el tope de gasto por defecto del SDK de x402. Los límites son trabajo del comprador. El trabajo del vendedor es ser lo bastante predecible para que un comprador con límites pueda seguir pagando.
Stellar ahora es un argumento de policy, no solo otra chain. Un dueño de agente que quiere un tope acumulado onchain sobre el gasto x402 puede obtenerlo hoy con el modelo de Soroban. El camino de firma de EVM no puede darlo. Eso hace que valga la pena evaluar x402 exact en Stellar como segundo rail.
Cómo mantenerse en la frontera
1. Correr la matriz de signers con scopes contra nuestro propio endpoint. Usar una wallet de staging de Crossmint en Base Sepolia para pagar nuestro endpoint walk-up en cinco configuraciones: un signer sin scopes; un scope de USDC con límite por debajo de la cotización; una lista de destinatarios que excluye nuestro payTo; un signer expirado; y un signer registrado con deployImmediately: false. Para cada una, registrar si Crossmint se niega a firmar, si el facilitator verifica y si la transferencia se liquida. Publicar los resultados. Eso convierte "no documentado" en un hecho.
2. Hacer que nuestra cotización quepa en los topes comunes de los compradores. Recortar la sobrecotización donde es estructural, empezando por imágenes y payloads binarios grandes. Publicar la cotización walk-up máxima por modelo con el max_tokens por defecto, para que un comprador pueda fijar un tope que no rechace llamadas normales.
3. Decidir la política para wallets contrafactuales. O agregar direcciones de factory verificadas a eip6492AllowedFactories, o devolver un error que le diga al comprador que despliegue la wallet primero. Una falla silenciosa en verify pierde un cliente que paga.
4. Publicar una guía para compradores con smart accounts. Una página. Registrar el signer del agente en el primer registro, vía REST, con un scope transfer de USDC, un interval, una lista recipients que incluya nuestro payTo y un expiresAt. Usar deployImmediately: true. Tratar cualquier maxAmount por llamada en el código del agente como orientativo.
5. Evaluar x402 exact en Stellar. Es el rail donde un tope acumulado a nivel de cuenta demostrablemente alcanza a un pago x402 hoy. Dimensionarlo después de la matriz en Base, para saber contra qué lo comparamos.
6. Vigilar una definición sobre typed data. Seguir la guía de scopes de Crossmint y la referencia de Create Signature. Cuando cualquiera de las dos diga cómo tratan los scopes a las firmas EIP-712, repetir el paso uno.
Los pasos uno y tres son baratos y nos dicen qué es verdad. El dos y el cuatro nos hacen fáciles de comprar con límites activos. El cinco es la apuesta más larga.
Véndeles a agentes que tienen límites
Inferencia compatible con OpenAI, pagada por llamada en USDC sobre x402.
Registra tu agente