x402 Payments

このページ

@emdash-cms/x402パッケージは、Cloudflare上の任意のAstroサイトにx402決済プロトコルのサポートを追加します。スタンドアロンのAstro統合として動作し、EmDashを使用する場合、EmDashのCMSフィールドと組み合わせてページごとの価格設定が可能です。

x402はHTTPネイティブな決済プロトコルです。クライアントが支払いなしで有料リソースをリクエストすると、サーバーは402 Payment Requiredと機械可読な支払い指示で応答します。x402を理解するエージェントとブラウザは、自動的に支払いを完了してリクエストを再試行できます。

使用するタイミング

最も一般的なユースケースはボット専用モードです:AIエージェントとスクレイパーにコンテンツアクセスを課金しながら、人間の訪問者は無料で読むことができます。これはCloudflare Bot Managementを使用してボットと人間を区別します。

すべての訪問者に支払いを強制することも、強制せずに支払いヘッダーをチェックすること(条件付きレンダリング)もできます。

インストール

パッケージマネージャーでパッケージをインストールします:

pnpm

pnpm add @emdash-cms/x402

npm

npm install @emdash-cms/x402

yarn

yarn add @emdash-cms/x402

セットアップ

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,
		}),
	],
});

TypeScriptがAstro.locals.x402を認識できるように型参照を追加します:

/// <reference types="@emdash-cms/x402/locals" />

基本的な使用方法

統合はAstro.locals.x402にエンフォーサーを配置します。ページのfrontmatterでenforce()を呼び出して、コンテンツを支払いの背後に保護します:

---
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>

enforce()メソッドは次のいずれかを返します:

  • Response (402) — クライアントは支払う必要があります。直接返します。
  • EnforceResult — リクエストは続行すべきです。コンテンツは支払われたか、強制がスキップされました(botOnlyモードの人間)。

ボット専用モード

botOnlytrueの場合、統合はrequest.cf.botManagement.scoreを読み取ってリクエストを分類します:

  • しきい値未満のスコア(デフォルト30)-> ボットとして扱われ、支払いが強制されます
  • しきい値以上のスコア-> 人間として扱われ、強制がスキップされます
  • ボット管理データなし(ローカル開発、非CF展開)-> 人間として扱われます

EnforceResultにはskippedフラグが含まれており、「支払う必要がなかった」と「支払った」を区別できます:

---
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)
---

EmDashでのページごとの価格設定

EmDashを使用する場合、ページごとの価格設定のためにコレクションに通常のnumberフィールドを追加し、リクエスト時にそれを読み取ります:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);

if (!entry) return Astro.redirect("/404");

const { x402 } = Astro.locals;

// Use the price from the CMS, falling back to a default
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>

強制せずに支払いをチェック

hasPayment()を使用して、リクエストに支払いヘッダーが含まれているかを検証や強制なしでチェックします。これは条件付きレンダリングに便利です — 支払った訪問者と支払っていない訪問者に異なるコンテンツを表示します:

---
const { x402 } = Astro.locals;

const hasPaid = x402.hasPayment(Astro.request);
---

{hasPaid ? (
  <p>Full premium content here.</p>
) : (
  <p>Subscribe for the full article.</p>
)}

設定リファレンス

オプションデフォルト説明
payTostring必須宛先ウォレットアドレス
networkstring必須CAIP-2ネットワーク識別子(例:eip155:8453
defaultPricePriceデフォルト価格、ページごとにオーバーライド可能
facilitatorUrlstringhttps://x402.org/facilitator決済ファシリテーターURL
schemestring"exact"決済スキーム
maxTimeoutSecondsnumber60決済署名の最大タイムアウト
evmbooleantrueEVMチェーンサポートを有効化
svmbooleanfalseSolanaチェーンサポートを有効化(@x402/svmが必要)
botOnlybooleanfalseボットのみに支払いを強制
botScoreThresholdnumber30ボットスコアしきい値(1-99、低い=ボットの可能性が高い)

価格フォーマット

価格はいくつかのフォーマットで指定できます:

  • ドル文字列"$0.10"$プレフィックスは削除され、値はそのまま渡されます)
  • 数値文字列"0.10"
  • 数値0.10
  • オブジェクト{ amount: "100000", asset: "0x...", extra: {} } 明示的なアセット/金額の場合

ネットワーク識別子

ネットワークはCAIP-2フォーマットを使用します:

ネットワーク識別子
Base mainneteip155:8453
Base Sepoliaeip155:84532
Ethereumeip155:1
Solanasolana:mainnet

Enforceオプション

特定のページの設定デフォルトをオーバーライドします:

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
});

Solanaサポート

Solanaはオプトインです。@x402/svmをインストールして設定で有効にします:

pnpm add @x402/svm

ネットワークをSolana識別子に設定し、Solanaのみを使用する場合はEVMを無効にします:

x402({
	payTo: "YourSolanaAddress",
	network: "solana:mainnet",
	svm: true,
	evm: false, // Disable EVM if only using Solana
});

仕組み

  1. x402()統合は、エンフォーサーを作成してAstro.locals.x402に配置するミドルウェアを登録します
  2. 設定はVite仮想モジュール(virtual:x402/config)を介してミドルウェアに渡されます
  3. enforce()が呼び出されると、リクエストのpayment-signatureヘッダーをチェックします
  4. 支払いヘッダーが存在しない場合、PAYMENT-REQUIREDヘッダーに支払い指示を含む402 Payment Requiredレスポンスが返されます
  5. 支払いヘッダーが存在する場合、ファシリテーターサービスを通じて検証され、決済されます
  6. 決済後、applyHeaders()を介してレスポンスにPAYMENT-RESPONSEヘッダーが設定されます

リソースサーバーは最初のリクエストでレイジー初期化され、ワーカーのライフタイム中キャッシュされます。