Pagamentos x402

Nesta página

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çãoTipoPredefinidoDescrição
payTostringobrigatórioEndereço da carteira de destino
networkstringobrigatórioIdentificador de rede CAIP-2 (ex. eip155:8453)
defaultPricePricePreço predefinido, substituível por página
facilitatorUrlstringhttps://x402.org/facilitatorURL do facilitador
schemestring"exact"Esquema de pagamento
maxTimeoutSecondsnumber60Tempo máximo para assinaturas de pagamento
evmbooleantrueAtivar cadeias EVM
svmbooleanfalseAtivar Solana (requer @x402/svm)
botOnlybooleanfalseExigir pagamento só para bots
botScoreThresholdnumber30Limiar 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úmero0.10
  • Objeto{ amount: "100000", asset: "0x...", extra: {} } para ativo/montante explícitos

Identificadores de rede

As redes usam o formato CAIP-2:

RedeIdentificador
Base mainneteip155:8453
Base Sepoliaeip155:84532
Ethereumeip155:1
Solanasolana: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

  1. A integração x402() regista middleware que cria o aplicador e coloca-o em Astro.locals.x402.
  2. A configuração passa ao middleware via módulo virtual Vite (virtual:x402/config).
  3. Ao chamar enforce(), verifica o cabeçalho payment-signature no pedido.
  4. Sem cabeçalho de pagamento, responde 402 Payment Required com instruções no cabeçalho PAYMENT-REQUIRED.
  5. Com cabeçalho, o pagamento é verificado e liquidado via facilitador.
  6. Após liquidação, applyHeaders() define PAYMENT-RESPONSE na resposta.

O servidor de recursos inicializa-se sob demanda no primeiro pedido e fica em cache durante a vida do worker.