O pacote @emdash-cms/x402 adiciona suporte ao protocolo de pagamento x402 a qualquer site Astro na Cloudflare. Funciona de forma autónoma — sem depender do núcleo EmDash — e combina bem com os campos CMS do EmDash para preços por página.
x402 é um protocolo de pagamento nativo de HTTP. Quando um cliente pede um recurso pago sem pagamento, o servidor responde com 402 Payment Required e instruções de pagamento legíveis por máquina. Agentes e browsers que compreendem x402 podem concluir o pagamento automaticamente e repetir o pedido.
Quando usar
O caso mais comum é o modo só bots: cobrar agentes de IA e scrapers pelo acesso ao conteúdo enquanto visitantes humanos leem de graça. Usa-se Cloudflare Bot Management para distinguir bots de humanos.
Também pode exigir pagamento a todos os visitantes, ou apenas verificar cabeçalhos de pagamento sem impor (renderização condicional).
Instalação
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 Configuração
Adicione a integração à configuração 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,
}),
],
});
Adicione a referência de tipos para o TypeScript reconhecer Astro.locals.x402:
/// <reference types="@emdash-cms/x402/locals" />
Utilização básica
A integração coloca um aplicador em Astro.locals.x402. Chame enforce() no frontmatter da página para proteger conteúdo atrás do pagamento:
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "Artigo premium",
});
// Sem pagamento válido, enforce() devolve uma Response 402.
// Devolva-a diretamente para enviar instruções de pagamento ao cliente.
if (result instanceof Response) return result;
// Pagamento verificado (ou ignorado em botOnly). Aplique cabeçalhos de resposta
// para prova de liquidação.
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>Conteúdo premium</h1>
</article>
enforce() devolve:
- uma
Response(402) — o cliente deve pagar; devolva-a diretamente. - um
EnforceResult— o pedido deve prosseguir. O conteúdo foi pago ou a aplicação foi ignorada (humano em botOnly).
Modo só bots
Com botOnly a true, a integração lê request.cf.botManagement.score:
- Pontuação abaixo do limiar (predefinição 30) → tratado como bot, pagamento exigido
- Pontuação no limiar ou acima → humano, aplicação ignorada
- Sem dados de bot management (dev local, não CF) → humano
EnforceResult inclui skipped para distinguir «não precisou de pagar» de «pagou»:
---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
// result.paid — true se o pagamento foi verificado
// result.skipped — true se a aplicação foi ignorada (humano em botOnly)
// result.payer — endereço da carteira do pagador (se pago)
---
Preço por página com EmDash
Com EmDash, pode adicionar um campo number à coleção para preço por página. Sem esquema especial — um campo CMS normal:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.redirect("/404");
const { x402 } = Astro.locals;
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 pagamento sem impor
Use hasPayment() para verificar se o pedido inclui cabeçalhos de pagamento sem verificar ou impor. Útil para renderização condicional — conteúdo diferente para quem pagou ou não:
---
const { x402 } = Astro.locals;
const hasPaid = x402.hasPayment(Astro.request);
---
{hasPaid ? (
<p>Conteúdo premium completo aqui.</p>
) : (
<p>Subscreva para o artigo completo.</p>
)}
Referência de configuração
| Opção | Tipo | Predefinido | Descrição |
|---|---|---|---|
payTo | string | obrigatório | Endereço da carteira de destino |
network | string | obrigatório | Identificador de rede CAIP-2 (ex. eip155:8453) |
defaultPrice | Price | — | Preço predefinido, substituível por página |
facilitatorUrl | string | https://x402.org/facilitator | URL do facilitador |
scheme | string | "exact" | Esquema de pagamento |
maxTimeoutSeconds | number | 60 | Tempo máximo para assinaturas de pagamento |
evm | boolean | true | Ativar cadeias EVM |
svm | boolean | false | Ativar Solana (requer @x402/svm) |
botOnly | boolean | false | Exigir pagamento só para bots |
botScoreThreshold | number | 30 | Limiar de pontuação de bot (1–99, mais baixo = mais bot) |
Formato de preço
Os preços podem ser especificados assim:
- String em dólares —
"$0.10"(o prefixo$é removido, o valor passa como está) - String numérica —
"0.10" - Número —
0.10 - Objeto —
{ amount: "100000", asset: "0x...", extra: {} }para ativo/montante explícitos
Identificadores de rede
As redes usam o formato CAIP-2:
| Rede | Identificador |
|---|---|
| Base mainnet | eip155:8453 |
| Base Sepolia | eip155:84532 |
| Ethereum | eip155:1 |
| Solana | solana:mainnet |
Opções de enforce
Substitua os predefinidos da config para uma página específica:
await x402.enforce(Astro.request, {
price: "$0.25",
payTo: "0xDifferentWallet",
network: "eip155:1",
description: "Artigo: Como funciona o x402",
mimeType: "text/html",
});
Suporte Solana
Solana é opcional. Instale @x402/svm e ative na config:
pnpm add @x402/svm
x402({
payTo: "YourSolanaAddress",
network: "solana:mainnet",
svm: true,
evm: false,
});
Como funciona
- A integração
x402()regista middleware que cria o aplicador e coloca-o emAstro.locals.x402. - A configuração passa ao middleware via módulo virtual Vite (
virtual:x402/config). - Ao chamar
enforce(), verifica o cabeçalhopayment-signatureno pedido. - Sem cabeçalho de pagamento, responde
402 Payment Requiredcom instruções no cabeçalhoPAYMENT-REQUIRED. - Com cabeçalho, o pagamento é verificado e liquidado via facilitador.
- Após liquidação,
applyHeaders()definePAYMENT-RESPONSEna resposta.
O servidor de recursos inicializa-se sob demanda no primeiro pedido e fica em cache durante a vida do worker.