← Blog
24 de octubre, 2026 · 13 min

x402 en BSV: el facilitator es la wallet del receptor

Todos los schemes x402 que hemos auditado hasta ahora permiten que un facilitator de terceros verifique y envíe el pago del buyer. Un scheme propuesto para BSV no puede hacerlo, por construcción: el destino se deriva de un secreto que solo comparten el pagador y el receptor, así que el facilitator tiene que ser la wallet del propio receptor. Esa decisión, y la de meter una transacción completa en un header, es donde empiezan las preguntas interesantes.

El PR #2890 agrega un scheme exact para BSV (Bitcoin SV) más un paquete de referencia en TypeScript, @x402/bsv. Lo abrió sirdeggen el 2026-07-17 y sigue abierto hoy, 2026-10-24. En el head que leímos, 22b6695 (un merge de upstream main del 2026-10-23), tiene 49 commits y 43 archivos cambiados. Tiene una review aprobatoria, de andyrowe (quien opera su propio servicio x402 sobre BSV) el 2026-08-19, y 39 comentarios. La descripción declara que fue redactado con asistencia de IA y revisado por el autor, y que una pasada de revisión adversarial produjo 24 correcciones.

Leímos la spec (specs/schemes/exact/scheme_exact_bsv.md, 173 líneas), el código del facilitator y del money parser, el hilo del PR y el perfil BRC-121 que adapta. Luego calculamos qué le hace el payload a un header. Este post cubre cinco cosas: qué se firma, quién puede verificarlo, cuánto crece el header, cómo se manejan el tiempo y el replay, y qué significa "settled".

Lo que envía el buyer

En EVM el buyer firma unos cientos de bytes de datos tipados. En BSV la wallet del buyer construye, fondea y firma una transacción real: un output P2PKH de exactamente amount satoshis, con el buyer pagando la fee del minero. No hay gas sponsorship ni contrato de token. asset es el string literal BSV.

La transacción viaja como BEEF, una serialización que lleva la transacción junto con su ancestría SPV (las transacciones padre y las Merkle paths necesarias para validarla sin un nodo). El payload tiene cinco campos:

// payload (PAYMENT-SIGNATURE, solo v2)
{
  transaction:       "AQEBAQFXpc4W8...",   // BEEF / Atomic BEEF en base64
  derivationPrefix:  "Cn0z2Q08xUzc",        // nonce aleatorio, mín. 8 bytes
  derivationSuffix:  "MTcwMDAwMDAwMDAwMA==", // base64(unix ms decimal)
  senderIdentityKey: "02ab1c...",           // clave comprimida de 33 bytes, hex
  outputIndex:       0
}

payTo no es una dirección. Es la clave pública de identidad BRC-100 de la wallet receptora, 66 caracteres hex, y nunca aparece on-chain. Cada pago paga una clave hija nueva derivada con BRC-42 desde esa clave de identidad, el prefix y el suffix. El suffix no es opaco: debe decodificar al tiempo Unix en milisegundos, en decimal, en que el cliente construyó el pago. Ese único campo cumple dos funciones: es un input de la derivación y es la prueba de frescura.

Por qué el facilitator tiene que ser el receptor

La derivación BRC-42 usa un secreto compartido ECDH entre pagador y receptor. Un tercero ve un output P2PKH y no puede saber si paga la clave derivada de payTo. La regla 7 de verificación en la spec es justamente ese chequeo, y la spec dice sin rodeos que un facilitator sin la wallet del receptor no puede hacerlo.

Por eso la spec agrega la regla 2: payTo debe ser igual a la clave de identidad de la propia wallet del facilitator, y el facilitator debe rechazar pagos para claves que no posee. Luego settle llama a internalizeAction de la wallet, que valida el BEEF por SPV, deriva la clave privada hija, confirma que el output la paga y toma custodia. La spec lista dos despliegues: in-process dentro del resource server, o un servicio facilitator self-hosted. Un facilitator multi-tenant compartido queda fuera de alcance. El PR nota dos caminos de extensión para ese caso, ninguno implementado: una variante de direccionamiento P2PKH plano (que abandona el modelo de privacidad) o una prueba de key-linkage (BRC-69, verificable con la prueba de Schnorr de BRC-94) que permitiría a un tercero verificar el destino sin custodia.

Compara con las rails que hemos auditado. En EVM, el facilitator paga gas y envía una transferencia. En Solana co-firma como fee payer. En Nano difunde un bloque que el buyer ya dejó válido. En las tres el facilitator es un servicio que el seller puede reemplazar. Aquí es una wallet que guarda los fondos del seller, dentro del proceso o la infraestructura del propio seller. No hay separación entre quien verifica y quien cobra.

Eso tiene una ventaja real. No hay un tercero a quien confiar la custodia y un pago no puede redirigirse. También tiene un costo: el "ecosistema de facilitators" que x402 asume (compartido, hospedado, intercambiable) no existe para este scheme. Un seller que quiere BSV opera una hot wallet.

El payload es una transacción, y los headers tienen límites

El transporte HTTP de x402 v2 pone todo el PaymentPayload en PAYMENT-SIGNATURE como JSON codificado en base64. El payload de BSV contiene transaction, que ya es BEEF en base64. Entonces el BEEF queda codificado en base64 dos veces: una dentro del JSON y otra alrededor del JSON. BRC-121, el perfil que este scheme adapta, envía el BEEF en un header x-bsv-beef codificado una sola vez. El envoltorio de x402 es estrictamente más grande.

El tamaño de un BEEF no lo fija el pago. BRC-62 exige cada transacción padre directa completa, así que lo fija el historial de la moneda (coin) que eligió la wallet. Un reporte de septiembre en el hilo del PR, del equipo detrás de la wallet del navegador Hodos, midió esto en mainnet con tres pagos consecutivos de una wallet a un sitio:

bytes BEEF   base64 en header   resultado
       779         ~1.0 KB      200
    92,837       123,784 B      431  "Exceeded maximum HTTP header buffer size of 100KB"
       849         ~1.1 KB      200

El pago del medio fue de 150 satoshis, entre los otros dos en monto. Fue un caso atípico solo por qué coin lo fondeó. El mismo reporte está en el tracker de BRC-121. Agrega tres puntos que vale la pena conservar. El presupuesto usable no es el tope citado, porque base64 cuesta un tercio extra y el tope cubre todo el bloque de headers (cookies, user agent y los demás headers x-bsv-*), lo que ellos ubican cerca de 68 a 73 KB de BEEF sobre un límite de 100 KB. Una regla de "preferir padres pequeños" es un orden, no una garantía: la suya estaba calibrada para un relay de 1 MiB y dejó pasar el bundle de 92,837 bytes. Y el fallo crece a medida que se usa la wallet.

Hicimos la aritmética para x402. Tomamos el PaymentPayload de ejemplo de la propia spec, reemplazamos transaction por un BEEF relleno de ceros de un tamaño dado, lo serializamos a JSON y codificamos el resultado en base64. Es un cálculo nuestro, no una medición contra un servidor real, y un payload real agrega unos cientos de bytes de campos.

bytes BEEF   BEEF en base64   valor de PAYMENT-SIGNATURE
       779          1,040             2,048 B
       849          1,132             2,172 B
    92,837        123,784           165,708 B

El mismo bundle de 92,837 bytes que desbordó un límite de 100 KB en forma BRC-121 pasa a unos 165 KB en forma x402. Dándole la vuelta a la pregunta, el BEEF más grande que cabe en una sola línea del header PAYMENT-SIGNATURE (nombre, separador y valor) bajo límites comunes es aproximadamente:

límite de la línea de header     BEEF máx.
  8,190 B  (default de aiohttp)   ~4.2 KB
 16,384 B  (16 KiB de Node)       ~8.8 KB
102,400 B  (100 KB)               ~57.2 KB

Son techos para ese único header. Las cookies y todos los demás headers salen del mismo bloque. Los dos pagos pequeños de arriba (779 y 849 bytes) pasan los tres. Un BEEF con un padre grande no pasa los dos primeros y, con 92,837 bytes, tampoco el tercero.

Cubrimos los límites de 8,190 y 16 KiB cuando miramos x402 desde el navegador. Allí el problema era un PAYMENT-REQUIRED que crecía con el catálogo del seller. Aquí el crecimiento está del lado del buyer, impulsado por un estado de wallet que el seller no ve y que la respuesta 402 no puede anunciar. El reporte de Hodos pide que BRC-121 publique un límite de tamaño, que el 402 pueda anunciar uno y que los senders revisen todo el bloque de headers antes de mintear. También plantea mover el bundle al body del request, lo que reconoce como un cambio de wire format. Nosotros agregaríamos un punto para x402: una pista de tamaño pertenece al campo extra de PaymentRequirements, que este scheme hoy deja vacío, para que un buyer pueda negarse antes de construir nada.

Tiempo: un campo, dos trabajos

La frescura se verifica contra el reloj del verificador. La ventana por defecto es de 30 segundos (DEFAULT_PAYMENT_WINDOW_MS = 30_000) y es simétrica: un timestamp más de 30 segundos en el futuro también se rechaza. En settle, el lado del pasado se extiende con maxTimeoutSeconds, para que un pago ya verificado no expire mientras corre el handler. Con el maxTimeoutSeconds de 60 del ejemplo de la spec, el deadline de settle es 90 segundos después del timestamp.

Dos implementadores en el hilo presionaron sobre esto. La wallet Hodos cachea un pago minteado por 25 segundos por URL y monto, lo que deja cinco segundos para el round-trip y el desfase de reloj entre pagador y verificador. Un pago reutilizado a los 24 segundos contra un verificador adelantado unos segundos falla por viejo, y el fallo parece intermitente. La spec no dice si un cliente puede reutilizar un payload. El segundo implementador, que opera una variante P2PKH plana de BSV exact en mainnet, sostiene que el timestamp sobre todo importa fragilidad por desfase de reloj, porque el replay ya lo atrapa el dedup de txid. Propone anclar la frescura a una expiración absoluta emitida por el servidor en el challenge, de modo que el reloj del pagador salga de la ecuación. En el último commit que leímos, el texto de la spec no tiene ninguno de los dos cambios.

La defensa contra replay es por capas y vale la pena leerla con cuidado. El facilitator mantiene un mapa en memoria de txids ya liquidados. En el código de referencia la entrada vive la ventana de settle más un margen de 60 segundos, con un piso de 600,000 ms. La entrada se escribe antes de que corra internalizeAction, así que dos llamadas /settle concurrentes no pueden competir contra la wallet, y se borra si la wallet lanza error o rechaza. Encima se usa el resultado isMerge de la wallet, pero solo junto con una cuenta de satoshis recién internalizados igual a cero. Un auto-pago devuelve isMerge: true en su primer settle, así que el flag solo rechazaría un pago legítimo. Y isMerge es una extensión de wallet-toolbox, no parte del BRC-100 base, así que una wallet conforme podría no reportarlo.

La consecuencia está en palabras de la propia spec: el registro de dedup es local al proceso, así que quienes operan varias instancias o reinician entre intentos deben enrutar un pago a una instancia consistente o usar un registro compartido. Es la misma forma del hallazgo del dedup en la capa que nadie revisa de nuestra auditoría de Sui. Nada on-chain impide un segundo settle del mismo BEEF hacia una wallet que no reporta merges. Lo impide la memoria del facilitator.

Qué significa "settled": cero confirmaciones

success: true refleja la aceptación por la wallet de una transacción que típicamente aún no está minada. La spec lo dice directo: el settlement es de cero confirmaciones, el pagador tiene las claves de los inputs hasta que se mina la transacción, y los operadores deben fijar precios en consecuencia. Recomienda propagar por un broadcaster como ARC justo después de la internalización, y permite retener success hasta que el broadcast sea aceptado. Es una promesa más débil que el caso de Base que medimos en nuestra auditoría de finality, donde un receipt preconfirmado aún precede a la finality. Aquí el buyer podría, en principio, gastar la misma coin en otro lado.

El resumen honesto es que el scheme sirve para lo que apunta BRC-121, montos pequeños por request, y lo dice. No es un scheme donde success pueda tomarse como final para un pago grande.

Dos reglas menores que merecen crédito

Igualdad estricta. BRC-121 acepta sobrepago. El scheme x402 exige que el output lleve exactamente amount. Un reviewer que construyó un gateway de comercio BSV lo defendió en el hilo: un checkout tiene recuperación humana ante un desajuste (reembolso, completar el pago), y un intercambio 402 máquina a máquina no la tiene, así que la igualdad deja solo dos resultados, pagado o reintentar. La deriva del precio se maneja fijando el monto en el challenge y acotándolo con la ventana de frescura.

Rechazar cadenas ambiguas. BSV, BTC y Bitcoin Cash comparten bloque génesis, así que un bip122:000000000019d6689c085ae165831e93 solo con génesis no puede decir a qué cadena se refiere. El scheme usa el namespace CAIP-2 registrado bsv (ChainAgnostic/namespaces#190, mergeado el 2026-08-18) con bsv:mainnet, bsv:testnet, bsv:ttn y bsv:tstn, y exige que clientes y facilitators rechacen identificadores ambiguos de la familia Bitcoin en vez de asumir BSV. Un pago del monto correcto en la cadena equivocada es irrecuperable, así que el rechazo duro es el default correcto. El facilitator además compara el getNetwork() de la wallet con la red solicitada. La auditoría de direcciones interoperables hizo el mismo argumento para payTo en otras cadenas.

Una dependencia de una API de tasas

Los precios se fijan en satoshis. Para precios en dólares el paquete incluye createWhatsOnChainMoneyParser, que convierte USD a satoshis con la API de exchange-rate de WhatsOnChain. Cachea una tasa 60 segundos y, si falla un refresh, sirve una tasa cacheada hasta 600 segundos antes de lanzar error. El smoke test en vivo de la descripción del PR valoró $0.001 en 7,440 satoshis con BSV a $13.44, lo que coincide con la aritmética. Como el monto queda fijado en los requirements, la tasa puede moverse sin romper un pago en vuelo.

El parser no tiene tasa cacheada en el arranque en frío, así que un primer fetch fallido lanza error. Llamamos al mismo endpoint dos veces desde este host hoy y obtuvimos HTTP 429 las dos veces. Puede ser un artefacto de una dirección compartida, y solo lo anotamos como recordatorio de que un feed de terceros sin autenticación forma parte del camino de disponibilidad del seller.

Qué significa para LLM4Agents

No operamos una rail BSV y este post no es un plan para hacerlo. El PR está abierto, su única aprobación es de otro implementador de BSV, y los ports de Go y Python están explícitamente diferidos. Pero el scheme deja al descubierto tres supuestos que nuestro stack hereda de EVM y Solana.

Primero, que el facilitator es un servicio intercambiable. En BSV es una hot wallet dentro del camino de settlement. Si alguna rail futura se parece a esto, "el facilitator" deja de ser una elección de proveedor y pasa a ser custodia que operamos, con el trabajo de gestión de claves y respuesta a incidentes que implica.

Segundo, que el header de pago es pequeño. Nuestros buyers envían una autorización firmada. Una rail cuyo payload embebe una transacción hace que el tamaño del header dependa del estado de la wallet. El lugar correcto para atrapar eso es un chequeo de tamaño del lado del buyer antes de firmar nada, y un límite anunciado en el 402.

Tercero, que un timestamp en el objeto firmado es barato. Solo es barato si los relojes del verificador y del pagador coinciden. Cualquier rail que estampa tiempo en el payload mete el desfase de reloj en nuestro presupuesto de fallos, y el fallo es intermitente por naturaleza.

Cómo mantenerse en la frontera

Pasos concretos, en orden.

Uno. Agregar una aserción de tamaño de payload al harness de pruebas del buyer: para cada scheme, construir el payload realista más grande y verificar que la línea PAYMENT-SIGNATURE codificada se mantenga bajo 8,190 bytes, reportando el margen. Hoy es una formalidad. Se vuelve la puerta de entrada para cualquier scheme que lleve una transacción.

Dos. Agregar al mismo harness una matriz de desfase de reloj: mover el reloj del pagador más y menos 5, 15 y 30 segundos contra cualquier scheme que lleve timestamp, y registrar qué combinaciones se rechazan. Eso convierte el argumento del cache de 25 segundos de este hilo en una prueba que podemos correr en nuestras propias rails.

Tres. Escribir, por rail, qué promete success: true: final, preconfirmado o aceptado pero sin minar. Nuestro trabajo de finality cubre EVM y Solana. Cero confirmaciones es una tercera clase, y una regla de piso de precio para ella debería existir antes que la rail.

Cuatro. Mantener la interfaz del facilitator honesta sobre quién tiene la clave. Si alguna vez abstraemos sobre rails, la interfaz debería permitir que una rail declare "la verificación requiere la clave del receptor", para que el enrutamiento nunca envíe ese pago a un facilitator compartido.

Cinco. Vigilar tres cosas en #2890: si la spec agrega una regla de reutilización o un modelo de frescura con expiración absoluta, si alguien propone una pista de tamaño o mueve el BEEF fuera del header, y si aparecen vectores known-answer para el chequeo de monto exacto, el suffix en milisegundos y las señales de replay. Un reviewer del hilo se ofreció a escribirlos cuando exista una segunda implementación. Sin vectores, dos ports van a discrepar justo en esas reglas.

Seis. Mantener a los vecinos a la vista. Los PRs de scheme para Canton y Bitcoin Cash también están abiertos. Cada rail de moneda nativa responde las mismas preguntas sobre quién verifica, qué se firma y cuándo es final. Ya tenemos tres respuestas archivadas, Nano, Sui y BSV, y no coinciden.

Paga por llamada, en stablecoins, sobre una API compatible con OpenAI

Un 402, una firma, 345+ modelos. Sin suscripción, sin mínimo.

Registra tu agente