Le paquet @emdash-cms/x402 ajoute la prise en charge du protocole de paiement x402 à tout site Astro sur Cloudflare. Il fonctionne seul — sans dépendre du cœur d’EmDash — et s’accorde bien avec les champs CMS d’EmDash pour un prix par page.
x402 est un protocole de paiement natif HTTP. Lorsqu’un client demande une ressource payante sans paiement, le serveur répond par 402 Payment Required et des instructions de paiement lisibles par machine. Les agents et navigateurs qui comprennent x402 peuvent régler le paiement automatiquement et relancer la requête.
Quand l’utiliser
Le cas le plus courant est le mode réservé aux bots : faire payer les agents IA et scrapers pour l’accès au contenu, tout en laissant les visiteurs humains lire gratuitement. Cloudflare Bot Management sert à distinguer bots et humains.
Vous pouvez aussi exiger le paiement pour tous les visiteurs, ou seulement vérifier la présence d’en-têtes de paiement sans appliquer de contrainte (rendu conditionnel).
Installation
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 Configuration
Ajoutez l’intégration à votre config 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,
}),
],
});
Ajoutez la référence de types pour que TypeScript connaisse Astro.locals.x402 :
/// <reference types="@emdash-cms/x402/locals" />
Utilisation de base
L’intégration place un enforceur sur Astro.locals.x402. Appelez enforce() dans le frontmatter de la page pour protéger le contenu par paiement :
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "Article premium",
});
// Sans paiement valide, enforce() renvoie une Response 402.
// Retournez-la directement pour envoyer les instructions au client.
if (result instanceof Response) return result;
// Paiement vérifié (ou ignoré en mode botOnly). Appliquer les en-têtes
// pour la preuve de règlement côté client.
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>Contenu premium</h1>
</article>
La méthode enforce() renvoie soit :
- une
Response(402) — le client doit payer ; retournez-la directement. - un
EnforceResult— la requête peut continuer. Le contenu a été payé, ou l’application du paiement a été ignorée (humain en mode botOnly).
Mode réservé aux bots
Lorsque botOnly vaut true, l’intégration lit request.cf.botManagement.score pour classer les requêtes :
- Score sous le seuil (30 par défaut) → traité comme bot, paiement exigé
- Score au seuil ou au-dessus → traité comme humain, contrôle ignoré
- Pas de données Bot Management (dev local, hors Cloudflare) → traité comme humain
EnforceResult inclut un indicateur skipped pour distinguer « n’a pas eu besoin de payer » et « a payé » :
---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
// result.paid — true si le paiement a été vérifié
// result.skipped — true si le contrôle a été ignoré (humain en botOnly)
// result.payer — adresse du portefeuille payeur (si payé)
---
Prix par page avec EmDash
Avec EmDash, vous pouvez ajouter un champ number à votre collection pour le prix par page. Aucun schéma spécial ni UI admin dédiée — un simple champ 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>
Vérifier le paiement sans l’exiger
Utilisez hasPayment() pour savoir si la requête contient des en-têtes de paiement, sans vérification ni application. Utile pour un rendu conditionnel — contenu différent pour visiteurs payants et non payants :
---
const { x402 } = Astro.locals;
const hasPaid = x402.hasPayment(Astro.request);
---
{hasPaid ? (
<p>Contenu premium complet ici.</p>
) : (
<p>Abonnez-vous pour l’article complet.</p>
)}
Référence de configuration
| Option | Type | Défaut | Description |
|---|---|---|---|
payTo | string | obligatoire | Adresse du portefeuille de destination |
network | string | obligatoire | Identifiant réseau CAIP-2 (ex. eip155:8453) |
defaultPrice | Price | — | Prix par défaut, surchargeable par page |
facilitatorUrl | string | https://x402.org/facilitator | URL du facilitateur de paiement |
scheme | string | "exact" | Schéma de paiement |
maxTimeoutSeconds | number | 60 | Délai max pour les signatures de paiement |
evm | boolean | true | Activer le support des chaînes EVM |
svm | boolean | false | Activer Solana (nécessite @x402/svm) |
botOnly | boolean | false | N’exiger le paiement que pour les bots |
botScoreThreshold | number | 30 | Seuil du score bot (1–99, plus bas = plus bot) |
Format des prix
Les prix peuvent être indiqués de plusieurs façons :
- Chaîne en dollars —
"$0.10"(le préfixe$est retiré, la valeur est transmise telle quelle) - Chaîne numérique —
"0.10" - Nombre —
0.10 - Objet —
{ amount: "100000", asset: "0x...", extra: {} }pour actif / montant explicites
Identifiants réseau
Les réseaux utilisent le format CAIP-2 :
| Réseau | Identifiant |
|---|---|
| Base mainnet | eip155:8453 |
| Base Sepolia | eip155:84532 |
| Ethereum | eip155:1 |
| Solana | solana:mainnet |
Options de enforce
Surchargez les valeurs par défaut pour une page donnée :
await x402.enforce(Astro.request, {
price: "$0.25",
payTo: "0xDifferentWallet",
network: "eip155:1",
description: "Article : fonctionnement de x402",
mimeType: "text/html",
});
Prise en charge de Solana
Solana est optionnel. Installez @x402/svm et activez-le dans la config :
pnpm add @x402/svm
x402({
payTo: "YourSolanaAddress",
network: "solana:mainnet",
svm: true,
evm: false,
});
Fonctionnement
- L’intégration
x402()enregistre un middleware qui crée un enforceur et le place surAstro.locals.x402 - La configuration est transmise au middleware via un module virtuel Vite (
virtual:x402/config) - Lors d’un appel à
enforce(), la présence de l’en-têtepayment-signatureest vérifiée - Sans en-tête de paiement, une réponse
402 Payment Requiredest renvoyée avec des instructions dans l’en-têtePAYMENT-REQUIRED - Avec un en-tête de paiement, celui-ci est vérifié via le facilitateur puis réglé
- Après règlement,
applyHeaders()définit les en-têtesPAYMENT-RESPONSEsur la réponse
Le serveur de ressources est initialisé à la demande lors du premier appel et mis en cache pour la durée de vie du worker.