Il pacchetto @emdash-cms/x402 aggiunge il supporto del protocollo di pagamento x402 a qualsiasi sito Astro su Cloudflare. Funziona in modo autonomo — senza dipendere dal core di EmDash — e si integra bene con i campi CMS di EmDash per prezzi per pagina.
x402 è un protocollo di pagamento nativo HTTP. Quando un client richiede una risorsa a pagamento senza aver pagato, il server risponde con 402 Payment Required e istruzioni di pagamento leggibili da macchina. Agent e browser che capiscono x402 possono completare il pagamento automaticamente e ripetere la richiesta.
Quando usarlo
Il caso d’uso più comune è la modalità solo bot: far pagare agent IA e scraper per l’accesso ai contenuti, lasciando i visitatori umani leggere gratis. Si usa Cloudflare Bot Management per distinguere bot e umani.
Puoi anche imporre il pagamento a tutti i visitatori, oppure controllare solo la presenza degli header di pagamento senza imporre nulla (rendering condizionale).
Installazione
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 Configurazione
Aggiungi l’integrazione alla config di 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,
}),
],
});
Aggiungi il riferimento ai tipi così TypeScript conosce Astro.locals.x402:
/// <reference types="@emdash-cms/x402/locals" />
Utilizzo base
L’integrazione espone un enforcer su Astro.locals.x402. Chiama enforce() nel frontmatter della pagina per proteggere i contenuti con il pagamento:
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "Articolo premium",
});
// Senza pagamento valido, enforce() restituisce una Response 402.
// Restituiscila direttamente per inviare le istruzioni al client.
if (result instanceof Response) return result;
// Pagamento verificato (o saltato in modalità botOnly). Applica gli header
// di risposta per la prova di regolamento.
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>Contenuto premium</h1>
</article>
Il metodo enforce() restituisce:
- una
Response(402) — il client deve pagare; restituiscila direttamente. - un
EnforceResult— la richiesta può procedere. Il contenuto è stato pagato oppure l’applicazione è stata saltata (umano in botOnly).
Modalità solo bot
Con botOnly impostato a true, l’integrazione legge request.cf.botManagement.score:
- Punteggio sotto la soglia (predefinita 30) → trattato come bot, pagamento richiesto
- Punteggio alla soglia o superiore → trattato come umano, controllo saltato
- Nessun dato Bot Management (dev locale, non Cloudflare) → trattato come umano
EnforceResult include il flag skipped per distinguere «non doveva pagare» da «ha pagato»:
---
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 il pagamento è stato verificato
// result.skipped — true se il controllo è stato saltato (umano in botOnly)
// result.payer — indirizzo wallet del pagante (se pagato)
---
Prezzo per pagina con EmDash
Con EmDash puoi aggiungere un campo number alla collection per il prezzo per pagina. Nessuno schema speciale o UI admin dedicata — un normale campo CMS:
---
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>
Verificare il pagamento senza imporlo
Usa hasPayment() per controllare se la richiesta include header di pagamento, senza verificare o imporre. Utile per rendering condizionale — contenuti diversi per visitatori che hanno pagato e per chi no:
---
const { x402 } = Astro.locals;
const hasPaid = x402.hasPayment(Astro.request);
---
{hasPaid ? (
<p>Qui il contenuto premium completo.</p>
) : (
<p>Abbonati per l’articolo completo.</p>
)}
Riferimento configurazione
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
payTo | string | obbligatorio | Indirizzo wallet di destinazione |
network | string | obbligatorio | Identificatore rete CAIP-2 (es. eip155:8453) |
defaultPrice | Price | — | Prezzo predefinito, sovrascrivibile per pagina |
facilitatorUrl | string | https://x402.org/facilitator | URL del facilitatore di pagamento |
scheme | string | "exact" | Schema di pagamento |
maxTimeoutSeconds | number | 60 | Timeout massimo per le firme di pagamento |
evm | boolean | true | Abilita supporto catene EVM |
svm | boolean | false | Abilita Solana (richiede @x402/svm) |
botOnly | boolean | false | Richiedi pagamento solo per i bot |
botScoreThreshold | number | 30 | Soglia score bot (1–99, più basso = più probabile bot) |
Formato prezzi
I prezzi possono essere specificati in diversi modi:
- Stringa in dollari —
"$0.10"(il prefisso$viene rimosso, il valore passa così com’è) - Stringa numerica —
"0.10" - Numero —
0.10 - Oggetto —
{ amount: "100000", asset: "0x...", extra: {} }per asset/importo espliciti
Identificatori di rete
Le reti usano il formato CAIP-2:
| Rete | Identificatore |
|---|---|
| Base mainnet | eip155:8453 |
| Base Sepolia | eip155:84532 |
| Ethereum | eip155:1 |
| Solana | solana:mainnet |
Opzioni di enforce
Sovrascrivi i default della config per una pagina specifica:
await x402.enforce(Astro.request, {
price: "$0.25",
payTo: "0xDifferentWallet",
network: "eip155:1",
description: "Articolo: come funziona x402",
mimeType: "text/html",
});
Supporto Solana
Solana è facoltativo. Installa @x402/svm e abilitalo nella config:
pnpm add @x402/svm
x402({
payTo: "YourSolanaAddress",
network: "solana:mainnet",
svm: true,
evm: false,
});
Come funziona
- L’integrazione
x402()registra middleware che crea un enforcer e lo mette suAstro.locals.x402 - La configurazione arriva al middleware tramite un modulo virtuale Vite (
virtual:x402/config) - Quando chiami
enforce(), viene controllato l’headerpayment-signaturesulla richiesta - Senza header di pagamento, viene restituita una risposta
402 Payment Requiredcon istruzioni nell’headerPAYMENT-REQUIRED - Con header di pagamento, questo viene verificato tramite il facilitatore e regolato
- Dopo il regolamento,
applyHeaders()imposta gli headerPAYMENT-RESPONSEsulla risposta
Il server delle risorse si inizializza in modo lazy alla prima richiesta e viene messo in cache per la durata del worker.