El paquete @emdash-cms/x402 agrega soporte para el protocolo de pago x402 a cualquier sitio Astro en Cloudflare. Se ejecuta como una integración Astro independiente y se combina con los campos CMS de EmDash para precios por página cuando usas EmDash.
x402 es un protocolo de pago nativo de HTTP. Cuando un cliente solicita un recurso de pago sin pago, el servidor responde con 402 Payment Required e instrucciones de pago legibles por máquina. Los agentes y navegadores que entienden x402 pueden completar el pago automáticamente y reintentar la solicitud.
Cuándo usar esto
El caso de uso más común es el modo solo bots: cobra a los agentes de IA y scrapers por el acceso al contenido mientras permites que los visitantes humanos lean gratis. Esto usa Cloudflare Bot Management para distinguir bots de humanos.
También puedes forzar el pago para todos los visitantes, o verificar encabezados de pago sin forzar (renderizado condicional).
Instalación
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 Configuración
Agrega la integración a tu configuración de Astro:
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",
botOnly: true,
botScoreThreshold: 30,
}),
],
});
Agrega la referencia de tipo para que TypeScript conozca Astro.locals.x402:
/// <reference types="@emdash-cms/x402/locals" />
Uso básico
La integración coloca un enforcer en Astro.locals.x402. Llama a enforce() en el frontmatter de tu página para proteger contenido detrás de 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
Response(402) — el cliente necesita pagar. Devuélvela directamente. - Un
EnforceResult— la solicitud debe continuar. El contenido fue pagado, o la aplicación fue omitida (humano en modo botOnly).
Modo solo bots
Cuando botOnly es true, la integración lee request.cf.botManagement.score para clasificar solicitudes:
- Puntuación por debajo del umbral (predeterminado 30) -> tratado como bot, pago forzado
- Puntuación en o por encima del umbral -> tratado como humano, aplicación omitida
- Sin datos de gestión de bots (desarrollo local, despliegue no-CF) -> tratado como humano
El EnforceResult incluye un flag 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)
---
Precios por página con EmDash
Al usar EmDash, agrega un campo regular number a tu colección para precios por página y léelo en tiempo de solicitud:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.redirect("/404");
const { x402 } = Astro.locals;
// Use the price from the CMS, falling back to a default
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>
Verificar pago sin forzar
Usa hasPayment() para verificar si una solicitud incluye encabezados de pago sin verificar o forzar. Esto es útil para renderizado condicional — mostrar contenido diferente a visitantes que pagaron vs. los que no:
---
const { x402 } = Astro.locals;
const hasPaid = x402.hasPayment(Astro.request);
---
{hasPaid ? (
<p>Full premium content here.</p>
) : (
<p>Subscribe for the full article.</p>
)}
Referencia de configuración
| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
payTo | string | requerido | Dirección de wallet de destino |
network | string | requerido | Identificador de red CAIP-2 (ej. eip155:8453) |
defaultPrice | Price | — | Precio predeterminado, sobrescribible por página |
facilitatorUrl | string | https://x402.org/facilitator | URL del facilitador de pago |
scheme | string | "exact" | Esquema de pago |
maxTimeoutSeconds | number | 60 | Tiempo de espera máximo para firmas de pago |
evm | boolean | true | Habilitar soporte de cadena EVM |
svm | boolean | false | Habilitar soporte de cadena Solana (requiere @x402/svm) |
botOnly | boolean | false | Solo forzar pago para bots |
botScoreThreshold | number | 30 | Umbral de puntuación de bot (1-99, menor = más probable que sea bot) |
Formato de precio
Los precios se pueden especificar en varios formatos:
- String de dólar —
"$0.10"(el prefijo$se elimina, el valor se pasa tal cual) - String numérico —
"0.10" - Número —
0.10 - Objeto —
{ amount: "100000", asset: "0x...", extra: {} }para cantidad/activo explícito
Identificadores de red
Las redes usan formato CAIP-2:
| Red | Identificador |
|---|---|
| Base mainnet | eip155:8453 |
| Base Sepolia | eip155:84532 |
| Ethereum | eip155:1 |
| Solana | solana:mainnet |
Opciones de Enforce
Sobrescribe los valores predeterminados de configuración para una página específica:
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
});
Soporte de Solana
Solana es opt-in. Instala @x402/svm y habilítalo en la configuración:
pnpm add @x402/svm
Establece la red a un identificador de Solana y deshabilita EVM si solo usas Solana:
x402({
payTo: "YourSolanaAddress",
network: "solana:mainnet",
svm: true,
evm: false, // Disable EVM if only using Solana
});
Cómo funciona
- La integración
x402()registra middleware que crea un enforcer y lo coloca enAstro.locals.x402 - La configuración se pasa al middleware a través de un módulo virtual de Vite (
virtual:x402/config) - Cuando se llama a
enforce(), verifica un encabezadopayment-signatureen la solicitud - Si no hay encabezado de pago presente, se devuelve una respuesta
402 Payment Requiredcon instrucciones de pago en el encabezadoPAYMENT-REQUIRED - Si hay un encabezado de pago presente, se verifica a través del servicio facilitador y se liquida
- Después de la liquidación, los encabezados
PAYMENT-RESPONSEse establecen en la respuesta a través deapplyHeaders()
El servidor de recursos se inicializa de forma lazy en la primera solicitud y se almacena en caché durante la vida útil del worker.