x402 Payments

Sur cette page

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

OptionTypeValeur par défautDescription
payTostringobligatoireAdresse du portefeuille de destination
networkstringobligatoireIdentifiant de réseau CAIP-2 (p. ex. eip155:8453)
defaultPricePrice—Prix par défaut, modifiable page par page
facilitatorUrlstringhttps://x402.org/facilitatorURL du facilitator de paiement
schemestring"exact"Schéma de paiement
maxTimeoutSecondsnumber60Délai d’expiration maximal des signatures de paiement
evmbooleantrueActiver la prise en charge des réseaux Ethereum Virtual Machine
svmbooleanfalseActiver la prise en charge de la Solana Virtual Machine (nécessite @x402/svm)
botOnlybooleanfalseN’imposer le paiement qu’aux bots
botScoreThresholdnumber30Seuil 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

  1. L’intégration place l’enforcer de paiement sur Astro.locals.x402 avant l’exécution de la page.
  2. enforce() recherche un en-tête payment-signature.
  3. Sans en-tête de paiement, enforce() renvoie une réponse 402 Payment Required dont le corps et l’en-tête PAYMENT-REQUIRED décrivent le paiement accepté.
  4. Avec un paiement correspondant, le facilitator configuré le vérifie et le règle.
  5. enforce() renvoie le payeur et le résultat du règlement. Appelez applyHeaders() pour que la réponse de la page inclue la preuve PAYMENT-RESPONSE du facilitator.