Web開発
X402、静的ブログの収益化練習
X402, a static blog monetization excercise (shtein.me)
要約
Cloudflareの新しいMonetization Gatewayの発表を受け、著者はx402プロトコルとCloudflare Workersを使用して、静的コンテンツの収益化システムを独自に実装しました。この記事では、このシステムを構築するプロセスと、マイクロペイメントによるコンテンツへのアクセス提供について解説しています。
全文翻訳
x402、静的ブログの収益化練習 2026-07-05
x402
Cloudflareは数日前にMonetization Gatewayのローンチに関する発表を行いました。これは多くの理由で革命的です。収益化への高い参入障壁を取り除き、課金対象と最低支払額を非常に精密に制御できます。これは文字通り、インターネットの使い方を変え、インターネットを支える広告経済を時代遅れにする可能性があります。ブログやAPIなどの収益化に理想的なツールのように思えます。そして、私には収益化できるものが何もありませんが、この技術は試さずにいられないほど重要だと思われます。そこで、そうしてみましょう。
計画
残念ながら、Cloudflareのサービスはまだクローズドベータ(?)のようで、現時点では自由にアクセスできず、ウェイティングリストに登録する必要があります。そこで、私たちに残されているのは、この仕組みを自分たちで実装することです。それはさらに楽しいですよね?幸いなことに、かなりシンプルなセットアップのようです。ただし、静的ブログにいくつかの可動部分が追加されますが、1日で追加できないようなものではありません。では、この仕組みを機能させるために何が必要でしょうか?
* 資金を受け取るためのウォレット
* 支払い処理ロジックを実行するためのワーカー
* ペイウォールしたいリソース
私は1と3を持っていますが、2はありません。この役割には、サイトをホストしているCloudflare Workersを使用することにします。これにより、実装する言語もTypeScriptに決まります。後でローカルサーバーでホストするように再実装するかもしれませんが、実験の範囲を管理可能に保つために、今回はCloudflareに留めましょう。
ミドルウェア
では、早速始めましょう。最初に必要なのは、リクエストを受け取り、支払い時のみアクセスを許可し、価格開示を処理するミドルウェアです。公式ドキュメントにはHonoでの実装例があるので、それを使用します。コードがスタンドアロンサーバーではなくCloudflare Workerで実行されること、ウォレットを配置すること、そして例のエンポイントをより有用なものに置き換えることを考慮して、いくつか変更を加えると、次のようになります。
```typescript
import { Hono } from "hono";
import type { MiddlewareHandler } from "hono";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { ExactSvmScheme } from "@x402/svm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const app = new Hono();
const evmAddress = "0xD040AEACCdFf083C5D4cB221F1533e8719a84F0e";
const svmAddress = "HjexCvNzgxJT3Ni7rb98WF4pQGEX2fN7G6Zh26B19mBc";
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator",
});
let payment: MiddlewareHandler | undefined;
app.use(async (c, next) => {
payment ??= paymentMiddleware(
{
"GET /api/joke": {
accepts: [
{
scheme: "exact",
price: "$0.01",
network: "eip155:84532",
payTo: evmAddress,
},
{
scheme: "exact",
price: "$0.01",
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
payTo: svmAddress,
},
],
description: "A premium, hand-picked joke",
mimeType: "application/json",
},
},
new x402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme())
.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme())
);
return payment(c, next);
});
const jokes = [];
app.get("/api/joke", (c) => {
return c.json({
joke: jokes[Math.floor(Math.random() * jokes.length)],
});
});
export default app;
```
Workerには`wrangler.jsonc`(Cloudflare固有の設定)も必要です。
```json
{
"name": "x402-poc",
"main": "src/index.ts",
"compatibility_date": "2026-07-01",
"compatibility_flags": ["nodejs_compat"],
"env": {
"production": {
"name": "x402-poc",
"routes": [
{
"pattern": "shtein.me/api/*",
"zone_name": "shtein.me"
}
]
}
}
}
```
そこには2つのあまり明白でないビットがあります。
`nodejs_compat`は、`x402`パッケージがNodeの組み込み(`events`、`crypto`、`url`)をインポートするため必要であり、これがないとビルドが失敗します。そして、ルートは意図的に本番環境のみに存在します。ルートが定義されていると、`wrangler dev`はそのルートのホストをエミュレートするため、ミドルウェアが生成するURLは`localhost`ではなく実際のドメインを指すことになります。
ローカルでの実行:
`npx wrangler dev`
そしてテストリクエストを送信すると、ペイウォールが表示されます。
```bash
curl -i http://localhost:8787/api/joke
```
```
HTTP/1.1 402 Payment Required
Content-Length: 2
Content-Type: application/json
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQYXltZW50IHJlcXVpcmVkIiwicmVzb3VyY2UiOnsidXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4Nzg3L2FwaS9qb2tlIiwiZGVzY3JpcHRpb24iOiJBIHByZW1pdW0sIGhhbmQtcGlja2VkIGpva2UiLCJtaW1lVHlwZSI6ImFwcGxpY2F0aW9uL2pzb24ifSwiYWNjZXB0cyI6W3sic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiZWlwMTU1Ojg0NTMyIiwiYW1vdW50IjoiMTAwMDAiLCJhc3NldCI6IjB4MDM2Q2JENTM4NDJjNTQyNjYzNGU3OTI5NTQxZUMyMzE4ZjNkQ0Y3ZSIsInBheVRvIjoiMHhEMDQwQUVBQ0NkRmYwODNDNUQ0Y0IyMjFGMTUzM2U4NzE5YTg0RjBlIiwibWF4VGltZW91dFNlY29uZHMiOjMwMCwiZXh0cmEiOnsibmFtZSI6IlVTREMiLCJ2ZXJzaW9uIjoiMiJ9fSx7InNjaGVtZSI6ImV4YWN0IiwibmV0d29yayI6InNvbGFuYTpFdFdUUkFCWmFZcTZpTWZlWUtvdVJ1MTY2VlUyeHFhMSIsImFtb3VudCI6IjEwMDAwIiwiYXNzZXQiOiI0ek1NQzlzcnQ1Umk1WDE0R0FnWGhhSGlpM0duUEFFRVJZUEpnWkpEbmNEVSIsInBheVRvIjoiSGpleEN2TnpneEpUM05pN3JiOThXRjRwUUdFWDJmTjdHNlpoMjZCMTltQmMiLCJtYXhUaW1lb3V0U2Vjb25kcyI6MzAwLCJleHRyYSI6eyJmZWVQYXllciI6IkNLUEtKV05kSkVxYTgxeDdDa1oxNEJWUGlZNnkxNlN4czdvd3pucXRXWXA1In19XX0=
```
`PAYMENT-REQUIRED`ヘッダーは、受け入れ可能な支払い方法と価格のリストをBase64エンコードしたものです。
```json
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://localhost:8787/api/joke",
"description": "A premium, hand-picked joke",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0xD040AEACCdFf083C5D4cB221F1533e8719a84F0e",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDC",
"version": "2"
}
},
{
"scheme": "exact",
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
"amount": "10000",
"asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
"payTo": "HjexCvNzgxJT3Ni7rb98WF4pQGEX2fN7G6Zh26B19mBc",
"maxTimeoutSeconds": 300,
"extra": {
"feePayer": "CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5"
}
}
]
}
```
そして、まさにこれが見たいものでした。2つのテストネットで正確に1セントの金額です。では、今すぐ支払ってみましょうか?この形式でテストするには、支払いを行い、確認付きでリクエストを再送信できるプログラム可能なクライアントが必要になります。しかし、この特定のユースケースでは、ブラウザのエンドユーザーができるかどうかを確認したいのです。そして幸いなことに、彼らはできます!今、同じURLをブラウザから訪れると、次のものが表示されます。
これは、これを機能させるために何をする必要があるかを平易な英語で教えてくれます。では、やってみましょう。
```bash
npm install @x402/paywall
```
```diff
import { HTTPFacilitatorClient } from "@x402/core/server";
+import { createPaywall, evmPaywall, svmPaywall } from "@x402/paywall";
new x402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme())
.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme())
+ {
+ appName: "Igor's corner",
+ testnet: true,
+ },
+ createPaywall().withNetwork(evmPaywall).withNetwork(svmPaywall).build(),
);
return payment(c, next);
});
```
そして、今アクセスを試みると、支払いを支援してくれる素敵なUIが表示されます。
現在テストネット上にあるため、トップアップリンクがあります。本番環境ではこれはありません。
MetaMaskで支払ってみましょう。
まずMetaMaskを接続します。
次に、実際の支払いに進みます。
そして最後に、支払ったコンテンツにアクセスできます。
この最終リクエストに関するいくつかの観察事項:
クライアントは`PAYMENT-SIGNATURE`ヘッダーに支払いを確認する署名を送信します。これは次のようにデコードされます。
```json
{
"x402Version": 2,
"payload": {
"authorization": {
"from": "0xAF40224e2B8fF2B4840c99d8458A0Fd803b15a0e",
"to": "0xD040AEACCdFf083C5D4cB221F1533e8719a84F0e",
"value": "10000",
"validAfter": "1783263532",
"validBefore": "1783264432",
"nonce": "0x82806d883f9d46e376b9dc9c065560521f1c9064cf58d1d4ea53e9e3be5f01df"
},
"signature": "0x7284c4c0da5cd029fff1f07fd4e5ff5b5c51bf13064b9a50e787f83f6e67555a419b85"
}
}
```