Polygon JPYC で x402 を動かす : 自作 Facilitatorで実機検証してみた
こんにちは!ブロックチェーン×AI Agentで自律経済圏を創るKomlock labでエンジニアをしている小原です!
最近、x402 で JPYC を扱えるようにするための修正が OSS コントリビュートされ、公式リポジトリにマージされたことを知りました。
その内容は note でも詳しく紹介されており、Polygon Amoy 上で JPYC を使った x402 の実行例が示されています。
ただ、私も同様に試してみたのですが、Polygon Amoy(Polygon のテストネット)ではJPYCを取得できず、検証そのものが進められない状態でした。
そのため今回は、
- x402 が Polygon をサポートしている
- Polygon Mainnet の JPYC を私が少額保有している
- 少額であれば本番ネットワークでも十分に検証可能である
といった理由から、Polygon Mainnet の JPYC を用いて検証を進めることにしました。
今回つくったもの
今回の実装では、Polygon Mainnet の JPYC を使って x402 決済を実際に動かし、
AI エージェント(Cursor MCP)から自動的に支払い付き API を叩ける環境を構築しました。
以下は、実際に Cursor が x402 決済を経由して天気 API を取得した際の画面です。
Cursorがx402決済を経由してAPIを取得した様子

このとき、Polygon Mainnet 上では 0.0000000000001 JPYC(100000 wei) の送金が実行されています。
Polygonscan で確認できる transferWithAuthorization のトランザクション

ここまで実際にMainnet JPYCで動くところを確認したうえで、
以下ではFacilitator の実装、x402 リポジトリへの組み込み方法を詳しく解説していきます。
想定読者
- x402 を自前トークンで動かしてみたい Web3 エンジニア
- EIP-712 署名〜transferWithAuthorization の実装を理解したい人
- AI エージェント × マイクロペイメントに興味がある人
Facilitatorについて
x402 の全体フローの中でFacilitatorが登場することは、以前こちらの記事で紹介しました。
今回は実装を扱うため、その役割をあらためて整理しておきます。
ユーザーの署名を用いて、代わりに送金を実行する仕組み
x402 では、ユーザーがウォレットで「支払いを許可する署名(authorization)」を生成し、
その署名をもとに 第三者が送金を代行するという形式を取ります。
Facilitator はまさにその「第三者」に相当し、
ユーザーの代わりにオンチェーンで送金を実行する役割を持ちます。
送金を実行するのはユーザー本人ではなく、
サーバー側が保持している送信用のウォレット(relayer) であり、
ガス代もサーバー側が負担します。
x402 における位置づけ
x402 は HTTP リクエストに「支払い」という概念を組み込む仕組みですが、
実際のオンチェーン送金はプロトコル外で行われます。
その部分を担うのが Facilitator です。
処理の流れは次のようになります。
- クライアントがウォレットで authorization(署名)を生成
- クライアント → サーバーへリクエスト
- サーバーが
/verifyAPI を呼び、署名・支払い内容を検証 - 問題なければ
/settleAPI を呼び、Facilitator が送金を実行 - 成功したらトランザクション情報を返す
この記事で扱う内容
この記事では、Polygon Mainnet の JPYC を用いて x402 決済を実際に動かすために、
次の 2 点を中心に進めていきます。
- Facilitator(/verify・/settle)を自前で実装する
- x402 公式リポジトリに自作 Facilitator を組み込み、Polygon JPYC で x402 が動作することを確認する
自作した Facilitator の挙動と、公式実装(express / MCP)の挙動を比較しながら、
x402 における支払いフロー全体を理解することを目的としています。
環境構築
セットアップ方法はGitHubにまとめています👇
ここでは、重要なところだけをピックアップして紹介します。
JPYC(Polygon Mainnet)と POL を準備する
Polygon Mainnet を利用するため、あらかじめ次の 2 種類のトークンを少額用意します。
- JPYC(Polygon Mainnet):実際の送金に使用
- POL(Gas 代):Facilitator がトランザクションを送信する際に必要
どちらも少額で問題ありません。
Polygon の JPYC は、公式の JPYC EX から取得できます。
取得後、Metamask の スワップ機能 を使って一部を POL に交換すれば、ガス代の準備も完了です。
Metamask Top 画面

スワップ画面

Alchemy で Polygon RPC を用意する
Facilitator は Polygon Mainnet に直接トランザクションを送信するため、
Mainnet の RPC エンドポイントが必要です。今回は Alchemy を利用します。
エンドポイントの取得手順は以下の通りです。
-
Alchemy にログイン(アカウントがなければ無料で作成できます)
-
「Create new app」から新規アプリを作成

-
Polygon POS を選択

-
作成されたアプリの Endpoint URL をコピー

Facilitatorの実装
x402 Facilitator の公式仕様
今回のFacilitatorは、Coinbaseのx402プロトコルに準拠します。
公式ドキュメントはこちら
-
Verify a payment
https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/verify-a-payment
-
Settle a payment
https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/settle-a-payment
Facilitatorは次の2つのAPIを提供する必要があります。
/verify
- authorization(EIP-712 署名)の妥当性を検証
- validAfter / validBefore、nonce、value、宛先、トークンなどが正しいか確認
- 残高があるか
- 署名を recover して from アドレスと一致するか確認
- 結果は
isValid/invalidReason/payerとして返す
/settle
-
/verifyを再実行し問題なければ送金へ進む - JPYC の
transferWithAuthorizationを呼び出し送金 - relayer が gas(POL)を負担
- 成功時は
transaction(txHash)を返す
この記事では、この仕様に基づき Polygon JPYC 用の Facilitator を自作します。
プロジェクト構成
今回のプロジェクトは以下のような構成になっています。
src/
├── server.ts # Expressサーバーのメインファイル
├── verifyService.ts # 署名検証ロジック
├── settleService.ts # トランザクション実行ロジック
├── common.ts # Viemクライアントの設定
├── jpycAbi.ts # JPYCコントラクトのABI
├── types.ts # TypeScript型定義
└── env.ts # 環境変数のバリデーション
必要なライブラリのインストール
pnpm add express cors viem dotenv
pnpm add -D typescript ts-node @types/node @types/express @types/cors
環境変数の設定
.envファイルを作成し、以下の変数を設定します。
RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY
RELAYER_PK=0x... # Facilitatorが使う秘密鍵(ガス代用)
JPYC_CONTRACT_ADDRESS=0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29
CHAIN_ID=137
JPYC コントラクトのセットアップ(common.ts)
viemを使ってPolygonに接続し、JPYCコントラクトのインスタンスを作成します。
export const jpycContract = getContract({
address: process.env.JPYC_CONTRACT_ADDRESS as `0x${string}`,
abi: jpycAbi,
client: {
public: publicClient,
wallet: walletClient,
},
});
/verify の実装(verifyService.ts)
/verifyエンドポイントでは、以下のチェックを行います。
export async function verifyAuthorization(req: VerifyRequest): Promise<VerifyResponse> {
const { authorization } = req.paymentPayload.payload;
// 1. x402プロトコルの整合性チェック
// - バージョン、スキーム、ネットワークの確認
// 2. Authorizationの妥当性チェック
// - アドレス、金額、送金先、有効期限の確認
if (auth.to !== requirements.payTo) {
return { isValid: false, invalidReason: "invalid_payload" };
}
// 3. nonceの重複チェック(リプレイ攻撃防止)
const authState = await jpycContract.read.authorizationState([
authorization.from,
authorization.nonce,
]);
if (Number(authState) !== 0) {
return { isValid: false, invalidReason: "invalid_payload" };
}
// 4. 残高チェック
const balance = await jpycContract.read.balanceOf([authorization.from]);
if (balance < BigInt(authorization.value)) {
return { isValid: false, invalidReason: "insufficient_funds" };
}
// 5. EIP-712署名検証
const domain = { name: "JPY Coin", version: "1", chainId: 137, /* ... */ };
const recovered = await recoverTypedDataAddress({
domain, types, message, signature
});
return {
isValid: recovered.toLowerCase() === authorization.from.toLowerCase(),
payer: authorization.from
};
}
/settle の実装(settleService.ts)
/settleエンドポイントでは、ブロックチェーン上でトランザクションを実行します。
export async function settleAuthorization(req: SettleRequest): Promise<SettleResponse> {
// 1. 署名を分解(r, s, v)
const signature = parseSignature(req.paymentPayload.payload.signature);
const v = signature.yParity + 27; // yParity 0/1 → v 27/28
// 2. transferWithAuthorizationを実行
const hash = await jpycContract.write.transferWithAuthorization([
authorization.from,
authorization.to,
BigInt(authorization.value),
BigInt(authorization.validAfter),
BigInt(authorization.validBefore),
authorization.nonce,
v, signature.r, signature.s,
]);
// 3. エラーハンドリング
// 残高不足、nonce重複、有効期限切れなどのエラーは
// スマートコントラクト側で検証されリバートされる
return { success: true, transaction: hash, /* ... */ };
}
Express サーバー
Expressでfacilitatorのエンドポイントを実装します。
import express from "express";
import { verifyAuthorization } from "./verifyService";
import { settleAuthorization } from "./settleService";
const app = express();
app.use(cors());
app.use(express.json());
// ヘルスチェック
app.get("/health", (req, res) => {
res.json({ status: "ok", service: "jpyc-x402-facilitator-polygon" });
});
// verify endpoint
app.post("/verify", async (req, res) => {
const result = await verifyAuthorization(req.body);
return res.status(200).json(result);
});
// settle endpoint
app.post("/settle", async (req, res) => {
const result = await settleAuthorization(req.body);
return res.status(200).json(result);
});
app.listen(4021, () => {
console.log("Facilitator running on http://localhost:4021");
});
デプロイ
実装が完成したら、あとは任意のホスティングサービスにデプロイすれば利用できます。
Vercel、Railway、Render、Fly.io など、Node.js が動くサービスであれば問題ありません。
今回私は Vercelにデプロイしましたが、手順としては非常にシンプルです。
- リポジトリを Vercel に接続
GitHub と連携し、プロジェクトを選択するだけでデプロイ準備が整います。
- 環境変数を設定
Vercel の Dashboard から、.envと同じ項目を設定します。
- RPC_URL
- RELAYER_PK
- JPYC_CONTRACT_ADDRESS
- CHAIN_ID
- デプロイされたらエンドポイントが利用可能に
Vercel がビルドに成功すると、
/verify/settle/health
といったエンドポイントが公開されます。
⚠️ 今回は本番ネットワークのため URL は非公開
今回の検証はPolygon Mainne のJPYCを実際に送金する内容なので、
セキュリティ・誤操作防止の観点から、この記事ではVercelの本番URLは公開していません。
サーバー側ウォレットを守るためにも、必要な人にだけ共有し、用途を限定して運用するのが適切です。
x402 公式リポジトリを使った検証
Facilitatorを作成したので、最後に実際に x402 決済を使った API サーバーとクライアントを動かしてみます。
今回は x402 公式リポジトリの examples を活用して実装を進めます。(x402-expressライブラリの最新版ではpolygon対応できていなかったため)
x402 リポジトリのクローン
まず、x402 の公式リポジトリをクローンします。
git clone https://github.com/coinbase/x402.git
cd x402
x402-express で天気情報 API を構築
公式リポジトリの Express サーバー example を使用します。
cd examples/typescript/servers/express
pnpm install
Express サーバーの設定
index.ts を以下のように修正します。
主な変更点は、Polygon Mainnet の JPYC を使用するための設定です。
asset 情報(address・decimals)などを手動で指定する必要があります。
import { config } from "dotenv";
import express from "express";
import { paymentMiddleware, Resource, type SolanaAddress } from "x402-express";
config();
const facilitatorUrl = process.env.FACILITATOR_URL as Resource;
const payTo = process.env.ADDRESS as `0x${string}` | SolanaAddress;
if (!facilitatorUrl || !payTo) {
console.error("Missing required environment variables");
process.exit(1);
}
const app = express();
app.use(
paymentMiddleware(
payTo,
{
"GET /weather": {
price: {
amount: "100000", // 0.0000000000001 JPYC(極小額でテスト)
asset: {
address: "0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29", // Polygon Mainnet JPYC
decimals: 18,
eip712: {
name: "JPY Coin",
version: "1",
},
},
},
network: "polygon",
},
},
{
url: facilitatorUrl,
},
),
);
app.get("/weather", (req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});
app.listen(4021, () => {
console.log(`Server listening at http://localhost:${4021}`);
});
環境変数の設定
.env ファイルを作成し、以下を設定します。
FACILITATOR_URL=https://your-facilitator.com
ADDRESS=0xYourRecipientAddress
- FACILITATOR_URL:先ほどデプロイした Facilitator の URL
- ADDRESS:支払いを受け取るアドレス
サーバーの起動
pnpm dev
これで http://localhost:4021/weather に x402 決済が必要な API サーバーが起動します。
x402-mcp で Cursor に統合する
次に、MCP (Model Context Protocol) サーバーを使って、Cursor から直接天気情報を取得できるようにします。
MCP サーバーの設定
公式リポジトリの MCP example を使用します。
cd ../../mcp # examples/typescript/mcp に移動
pnpm install
MCP サーバーの環境変数
.env ファイルを作成します。
PRIVATE_KEY=0xYourPrivateKey
RESOURCE_SERVER_URL=http://localhost:4021
ENDPOINT_PATH=/weather
Cursor MCP 設定ファイルへの追加
Cursor の MCP 設定ファイル ~/.cursor/mcp.json に以下を追加します。
{
"mcpServers": {
"weather": {
"command": "pnpm",
"args": ["--silent", "-C", "/Users/XXXX/x402/examples/typescript/mcp", "dev"]
}
}
}
Cursor で動作確認
- Express サーバーを起動(別のターミナルで)
cd examples/typescript/servers/express
pnpm dev
- Cursor を再起動
MCP サーバーが自動的に起動します。
- Cursor で天気情報を取得
Cursor のチャットで以下のように依頼します。
天気情報を取得してください
Cursor が get-data-from-resource-server ツールを自動的に呼び出し、x402 決済を通じて天気情報を取得します。
このとき、裏側では以下の処理が行われています。
- MCP サーバーが
/weatherエンドポイントにリクエスト - 402 Payment Required レスポンスを受け取る
- EIP-712 署名を自動生成
-
X-PAYMENTヘッダーに署名を付与して再リクエスト - サーバーが Facilitator で検証・決済を実行
- 天気情報を取得して Cursor に返す
まとめ
この記事では、Polygon Mainnet の JPYC を使った x402 決済の実装を、以下の流れで解説しました。
- Facilitator の自作(/verify・/settle の実装とデプロイ)
- x402-express で天気 API サーバーを構築(公式 example を活用)
- x402-mcp で Cursor に統合(MCP 設定のみで完結)
特に重要なポイントは以下の 3 点です。
1. x402 の仕組みを理解する
x402 は HTTP リクエストに「支払い」を組み込む新しいプロトコルです。
- クライアントが EIP-712 署名を生成
- サーバーが Facilitator で検証・決済
- すべて自動化されており、開発者は意識する必要がない
2. Facilitator の役割
Facilitator は x402 決済の要となる存在です。
-
/verify:署名の妥当性を検証 -
/settle:実際のオンチェーン送金を実行 - 公式 Facilitator もありますが、自作することで仕組みを深く理解できる
3. AI エージェントとの統合
MCP を使うことで、AI エージェント(Cursor、Claude Desktop など)が自律的に決済を行いながら API を利用できます。
これは、将来的に AI エージェントが様々なサービスを自動的に購入・利用する世界観を実現する重要な技術です。
今後の展望
x402 は、以下のようなユースケースへの応用が期待されています。
- API のマイクロペイメント:1 リクエストごとに課金
- AI エージェントの自律的なサービス利用:人間の介入なしに決済
- 従量課金型のデジタルコンテンツ配信:記事 1 本、動画 1 本ごとに課金
JPYC の岡部さんも、JPYC と AI エージェントの親和性について、
「AI が JPYC を使っていく未来」を示唆する投稿をされています。
今回の検証は、JPYC を AI エージェントで扱う場合の一つの動作例として機能します。
また、
ではすでに x402 を使ったプロダクトが次々と登場しており、
AI × マイクロペイメントの流れは今後ますます強まっていくはずです。
記事が参考になった方は、ぜひ Zenn のフォローや
X(@brto_0224) のフォローもよろしくお願いします! 🙌
参考資料
Discussion
凄!!参考になりました!!