Le paquet @emdash-cms/x402 ajoute la prise en charge du protocole de paiement x402 aux sites Astro rendus côté serveur. Il s’agit d’une intégration Astro autonome : l’application des paiements ne nécessite donc pas EmDash. Lorsque le site utilise aussi EmDash, un champ de contenu peut fournir le prix de chaque entrée.
x402 est un protocole de paiement natif HTTP. Lorsqu’un client demande une ressource payante sans avoir payé, le serveur répond par 402 Payment Required accompagné d’instructions de paiement lisibles par une machine. Les agents et les navigateurs qui comprennent x402 peuvent effectuer le paiement automatiquement et relancer la requête.
Choisir un mode d’application
Utilisez l’application normale lorsque chaque requête vers la route doit présenter un paiement valide. Ce mode ne dépend pas des métadonnées de requête propres à Cloudflare.
Utilisez le mode bots uniquement lorsque le site s’exécute sur Cloudflare Workers avec Bot Management activé et que seules les requêtes à faible score doivent payer. Le paquet lit request.cf.botManagement.score ; lorsque cette valeur est absente, il traite la requête comme humaine et ignore l’application. N’utilisez pas le mode bots uniquement lorsque l’absence de données sur les bots doit échouer en mode fermé (fail closed).
hasPayment() offre un troisième comportement, réservé à la présentation. Il indique si la requête comporte un en-tête de paiement, mais ne vérifie ni ne règle le paiement.
Installer le paquet
Installez le paquet avec votre gestionnaire de paquets :
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 Configurer l’intégration
Choisissez un portefeuille, un réseau et un facilitator qui fonctionnent ensemble. L’intégration prend en charge par défaut les réseaux Ethereum Virtual Machine (EVM), mais le facilitator doit lui aussi prendre en charge le réseau et l’actif configurés. L’exemple suivant utilise le mainnet de Base et impose le paiement pour chaque requête :
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",
}),
],
});
Ajoutez la référence de types pour que TypeScript connaisse Astro.locals.x402 :
/// <reference types="@emdash-cms/x402/locals" />
Imposer le paiement sur une route
L’intégration place un enforcer sur Astro.locals.x402. Appelez enforce() dans le frontmatter de votre page pour placer le contenu derrière un paiement :
---
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>
La méthode enforce() renvoie l’un des deux éléments suivants :
- Une
Response(402) — le client doit payer. Renvoyez-la directement. - Un
EnforceResult— la requête doit se poursuivre. Le contenu a été payé, ou l’application a été ignorée (humain en mode botOnly).
Activer le mode bots uniquement
Activez botOnly dans la configuration de l’intégration :
x402({
payTo: "0xYourWalletAddress",
network: "eip155:8453",
defaultPrice: "$0.01",
botOnly: true,
botScoreThreshold: 30,
});
L’intégration lit request.cf.botManagement.score de Cloudflare pour classer les requêtes :
- Score inférieur au seuil (30 par défaut) -> traité comme un bot, paiement imposé
- Score égal ou supérieur au seuil -> traité comme un humain, application ignorée
- Aucune donnée Bot Management (développement local, déploiement hors Cloudflare) -> traité comme un humain
Le EnforceResult comprend un indicateur skipped qui permet de distinguer « n’avait pas à payer » de « 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 if payment was verified
// result.skipped — true if enforcement was skipped (human in botOnly mode)
// result.payer — wallet address of payer (if paid)
---
Lire un prix depuis EmDash
Avec EmDash, ajoutez un champ number ordinaire à votre collection pour une tarification par page et lisez-le au moment de la requête :
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.rewrite("/404");
const { x402 } = Astro.locals;
// Use the price from the CMS, falling back only when it is absent.
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 la présence d’un en-tête de paiement sans imposer le paiement
Utilisez hasPayment() pour vérifier si une requête comporte un en-tête de paiement, sans le vérifier ni l’imposer. Cela peut modifier une invite ou un message de connexion, mais ne doit jamais déverrouiller un contenu protégé :
---
const { x402 } = Astro.locals;
const hasPaymentHeader = x402.hasPayment(Astro.request);
---
{hasPaymentHeader ? (
<p>Payment supplied. Verification is still required.</p>
) : (
<p>This page requires payment.</p>
)}
Référence de configuration
| Option | Type | Valeur par défaut | Description |
|---|---|---|---|
payTo | string | obligatoire | Adresse du portefeuille de destination |
network | string | obligatoire | Identifiant de réseau CAIP-2 (p. ex. eip155:8453) |
defaultPrice | Price | — | Prix par défaut, modifiable page par page |
facilitatorUrl | string | https://x402.org/facilitator | URL du facilitator de paiement |
scheme | string | "exact" | Schéma de paiement |
maxTimeoutSeconds | number | 60 | Délai d’expiration maximal des signatures de paiement |
evm | boolean | true | Activer la prise en charge des réseaux Ethereum Virtual Machine |
svm | boolean | false | Activer la prise en charge de la Solana Virtual Machine (nécessite @x402/svm) |
botOnly | boolean | false | N’imposer le paiement qu’aux bots |
botScoreThreshold | number | 30 | Seuil du score de bot (1-99 ; plus il est bas, plus la requête est probablement un bot) |
Format du prix
Les prix peuvent être indiqués dans plusieurs formats :
- 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 indiquer explicitement l’actif et le montant
Identifiants de réseau
CAIP-2 attribue aux réseaux blockchain des identifiants non ambigus, tels que eip155:8453 pour le mainnet de Base. Utilisez l’identifiant exact annoncé par le facilitator. Le paquet accepte la famille eip155:* lorsque la prise en charge d’EVM est activée, et la famille solana:* lorsque la prise en charge de la Solana Virtual Machine (SVM) est activée ; cela ne garantit pas qu’un facilitator accepte tous les réseaux de cette famille.
Remplacer les réglages pour une seule requête
Remplacez les valeurs par défaut de la configuration pour une page précise :
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
});
Activer la prise en charge de Solana
Solana est optionnel. Installez @x402/svm et activez-le dans la configuration :
pnpm add @x402/svm
Définissez svm: true, utilisez l’identifiant CAIP-2 de Solana pris en charge par le facilitator, et désactivez EVM si le site n’accepte que les paiements Solana. Par exemple, l’identifiant du mainnet de Solana utilisé par @x402/svm 2.8 est solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp :
x402({
payTo: "YourSolanaAddress",
network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
svm: true,
evm: false, // Disable EVM if only using Solana
});
Déroulement d’une requête
- L’intégration place l’enforcer de paiement sur
Astro.locals.x402avant l’exécution de la page. enforce()recherche un en-têtepayment-signature.- Sans en-tête de paiement,
enforce()renvoie une réponse402 Payment Requireddont le corps et l’en-têtePAYMENT-REQUIREDdécrivent le paiement accepté. - Avec un paiement correspondant, le facilitator configuré le vérifie et le règle.
enforce()renvoie le payeur et le résultat du règlement. AppelezapplyHeaders()pour que la réponse de la page inclue la preuvePAYMENT-RESPONSEdu facilitator.