x402 Payments

En esta página

El paquete @emdash-cms/x402 añade compatibilidad con el protocolo de pago x402 a los sitios de Astro renderizados en el servidor. Es una integración de Astro independiente, por lo que aplicar los pagos no requiere EmDash. Cuando el sitio también usa EmDash, un campo de contenido puede proporcionar el precio de cada entrada.

x402 es un protocolo de pago nativo de HTTP. Cuando un cliente solicita un recurso de pago sin haber pagado, el servidor responde con 402 Payment Required y con instrucciones de pago legibles por máquina. Los agentes y navegadores que entienden x402 pueden completar el pago automáticamente y reintentar la solicitud.

Elegir un modo de aplicación

Usa la aplicación normal cuando cada solicitud a la ruta deba presentar un pago válido. Este modo no depende de metadatos de solicitud específicos de Cloudflare.

Usa el modo solo bots cuando el sitio se ejecute en Cloudflare Workers con Bot Management activado y solo deban pagar las solicitudes con puntuación baja. El paquete lee request.cf.botManagement.score; cuando ese valor no existe, trata la solicitud como humana y omite la aplicación. No uses el modo solo bots cuando la ausencia de datos de bots deba fallar de forma cerrada (fail closed).

hasPayment() ofrece un tercer comportamiento, solo para presentación. Indica si la solicitud tiene una cabecera de pago, pero no verifica ni liquida el pago.

Instalar el paquete

Instala el paquete con tu gestor de paquetes:

pnpm

pnpm add @emdash-cms/x402

npm

npm install @emdash-cms/x402

yarn

yarn add @emdash-cms/x402

Configurar la integración

Elige una billetera, una red y un facilitator que funcionen juntos. La integración admite por defecto las redes de la Ethereum Virtual Machine (EVM), pero el facilitator también debe ser compatible con la red y el activo configurados. El siguiente ejemplo usa la red principal de Base y exige el pago en cada solicitud:

import { defineConfig } from "astro/config";
import { x402 } from "@emdash-cms/x402";

export default defineConfig({
	integrations: [
		x402({
			payTo: "0xYourWalletAddress",
			network: "eip155:8453", // Base mainnet
			defaultPrice: "$0.01",
		}),
	],
});

Añade la referencia de tipos para que TypeScript conozca Astro.locals.x402:

/// <reference types="@emdash-cms/x402/locals" />

Exigir el pago en una ruta

La integración coloca un enforcer en Astro.locals.x402. Llama a enforce() en el frontmatter de tu página para poner el contenido tras un pago:

---
const { x402 } = Astro.locals;

const result = await x402.enforce(Astro.request, {
  price: "$0.05",
  description: "Premium article",
});

// If the request has no valid payment, enforce() returns a 402 Response.
// Return it directly to send payment instructions to the client.
if (result instanceof Response) return result;

// Payment verified (or skipped in botOnly mode). Apply response headers
// so the client gets settlement proof.
x402.applyHeaders(result, Astro.response);
---

<article>
  <h1>Premium content</h1>
</article>

El método enforce() devuelve una de estas dos cosas:

  • Una Response (402) — el cliente debe pagar. Devuélvela directamente.
  • Un EnforceResult — la solicitud debe continuar. El contenido se pagó o se omitió la aplicación (humano en modo botOnly).

Activar el modo solo bots

Activa botOnly en la configuración de la integración:

x402({
	payTo: "0xYourWalletAddress",
	network: "eip155:8453",
	defaultPrice: "$0.01",
	botOnly: true,
	botScoreThreshold: 30,
});

La integración lee request.cf.botManagement.score de Cloudflare para clasificar las solicitudes:

  • Puntuación por debajo del umbral (30 por defecto) -> se trata como bot y se exige el pago
  • Puntuación igual o superior al umbral -> se trata como humano y se omite la aplicación
  • Sin datos de Bot Management (desarrollo local, despliegue fuera de Cloudflare) -> se trata como humano

El EnforceResult incluye una marca skipped para que puedas distinguir entre «no necesitó pagar» y «pagó»:

---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;

x402.applyHeaders(result, Astro.response);

// result.paid    — true if payment was verified
// result.skipped — true if enforcement was skipped (human in botOnly mode)
// result.payer   — wallet address of payer (if paid)
---

Leer un precio desde EmDash

Al usar EmDash, añade un campo number normal a tu colección para fijar precios por página y léelo en el momento de la solicitud:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);

if (!entry) return Astro.rewrite("/404");

const { x402 } = Astro.locals;

// Use the price from the CMS, falling back only when it is absent.
const result = await x402.enforce(Astro.request, {
  price: entry.data.price ?? "$0.01",
  description: entry.data.title,
});
if (result instanceof Response) return result;

x402.applyHeaders(result, Astro.response);
---

<article>
  <h1>{entry.data.title}</h1>
</article>

Comprobar una cabecera de pago sin exigirla

Usa hasPayment() para comprobar si una solicitud incluye una cabecera de pago sin verificarla ni exigirla. Esto puede cambiar un aviso o un mensaje de inicio de sesión, pero nunca debe desbloquear contenido protegido:

---
const { x402 } = Astro.locals;

const hasPaymentHeader = x402.hasPayment(Astro.request);
---

{hasPaymentHeader ? (
  <p>Payment supplied. Verification is still required.</p>
) : (
  <p>This page requires payment.</p>
)}

Referencia de configuración

OpciónTipoValor por defectoDescripción
payTostringobligatorioDirección de la billetera de destino
networkstringobligatorioIdentificador de red CAIP-2 (p. ej., eip155:8453)
defaultPricePrice—Precio por defecto, que se puede anular por página
facilitatorUrlstringhttps://x402.org/facilitatorURL del facilitator de pagos
schemestring"exact"Esquema de pago
maxTimeoutSecondsnumber60Tiempo de espera máximo para las firmas de pago
evmbooleantrueActivar la compatibilidad con redes de la Ethereum Virtual Machine
svmbooleanfalseActivar la compatibilidad con la Solana Virtual Machine (requiere @x402/svm)
botOnlybooleanfalseExigir el pago solo a los bots
botScoreThresholdnumber30Umbral de puntuación de bot (1-99; cuanto más bajo, más probable que sea un bot)

Formato de precio

Los precios se pueden especificar en varios formatos:

  • Cadena en dólares — "$0.10" (se elimina el prefijo $ y el valor se pasa tal cual)
  • Cadena numérica — "0.10"
  • Número — 0.10
  • Objeto — { amount: "100000", asset: "0x...", extra: {} } para indicar el activo y el importe de forma explícita

Identificadores de red

CAIP-2 asigna identificadores inequívocos a las redes blockchain, como eip155:8453 para la red principal de Base. Usa el identificador exacto que anuncia el facilitator. El paquete acepta la familia eip155:* cuando la compatibilidad con EVM está activada y la familia solana:* cuando está activada la compatibilidad con la Solana Virtual Machine (SVM); esto no garantiza que un facilitator acepte todas las redes de esa familia.

Anular una sola solicitud

Anula los valores por defecto de la configuración para una página concreta:

await x402.enforce(Astro.request, {
	price: "$0.25", // Override price
	payTo: "0xDifferentWallet", // Override wallet
	network: "eip155:1", // Override network
	description: "Article: How x402 Works", // Resource description
	mimeType: "text/html", // MIME type hint
});

Activar la compatibilidad con Solana

Solana es opcional. Instala @x402/svm y actívalo en la configuración:

pnpm add @x402/svm

Establece svm: true, usa el identificador CAIP-2 de Solana que admita el facilitator y desactiva EVM si el sitio solo acepta pagos en Solana. Por ejemplo, el identificador de la red principal de Solana que usa @x402/svm 2.8 es solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:

x402({
	payTo: "YourSolanaAddress",
	network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
	svm: true,
	evm: false, // Disable EVM if only using Solana
});

Flujo de una solicitud

  1. La integración coloca el enforcer de pagos en Astro.locals.x402 antes de que se ejecute la página.
  2. enforce() busca una cabecera payment-signature.
  3. Sin cabecera de pago, enforce() devuelve una respuesta 402 Payment Required cuyo cuerpo y cabecera PAYMENT-REQUIRED describen el pago aceptado.
  4. Con un pago que coincida, el facilitator configurado lo verifica y lo liquida.
  5. enforce() devuelve el pagador y el resultado de la liquidación. Llama a applyHeaders() para que la respuesta de la página incluya la prueba PAYMENT-RESPONSE del facilitator.