コンテンツにスキップ

x402 ペイメント

@emdash-cms/x402 パッケージは、Cloudflare上の任意のAstroサイトに x402支払いプロトコル サポートを追加します。EmDashコアへの依存はなく単独で動作しますが、ページ単位の価格設定にはEmDashのCMSフィールドと組み合わせて使用すると効果的です。

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

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

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

Terminal window
pnpm add @emdash-cms/x402

Astro設定にインテグレーションを追加します:

js title="astro.config.mjs"
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 を認識できるように型参照を追加します:

ts title="src/env.d.ts"
/// <reference types="@emdash-cms/x402/locals" />

このインテグレーションは、Astro.locals.x402 にエンフォーサーを配置します。ページのフロントマターで enforce() を呼び出して、コンテンツを支払いの後ろに制限します:

astro title="src/pages/posts/[...slug].astro"
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "Premium article",
});
// リクエストに有効な支払いがない場合、enforce() は402 Responseを返します。
// それを直接返して、クライアントに支払い指示を送信します。
if (result instanceof Response) return result;
// 支払いが検証された(またはbotOnlyモードでスキップされた)。レスポンスヘッダーを適用して、
// クライアントが決済証明を受け取れるようにします。
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>プレミアムコンテンツ</h1>
</article>

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

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

botOnly が true の場合、インテグレーションは 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
// result.skipped — 強制がスキップされた場合はtrue(botOnlyモードでの人間)
// result.payer — 支払い者のウォレットアドレス(支払い済みの場合)
---

EmDashを使用したページ単位の価格設定

Section titled “EmDashを使用したページ単位の価格設定”

EmDashを使用する場合、コレクションに number フィールドを追加してページ単位の価格設定ができます。特別なスキーマや管理UIは不要です — 通常のCMSフィールドのみで可能です:

astro title="src/pages/posts/[...slug].astro"
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.redirect("/404");
const { x402 } = Astro.locals;
// CMSからの価格を使用し、デフォルトにフォールバック
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>

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

Section titled “強制せずに支払いをチェックする”

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

特定のページに対して設定のデフォルトを上書きします:

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はオプトインです。@x402/svmをインストールし、設定で有効にしてください:

Terminal window
pnpm add @x402/svm
js title="astro.config.mjs"
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. 支払いヘッダーが存在しない場合、402 Payment Required レスポンスが返され、支払い手順が PAYMENT-REQUIRED ヘッダーに含まれます
  5. 支払いヘッダーが存在する場合、ファシリテーターサービスを介して検証され、決済されます
  6. 決済後、applyHeaders() を介してレスポンスに PAYMENT-RESPONSE ヘッダーが設定されます

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