✴️

Polygon JPYC で x402 を動かす : 自作 Facilitatorで実機検証してみた

に公開1

こんにちは!ブロックチェーン×AI Agentで自律経済圏を創るKomlock labでエンジニアをしている小原です!

最近、x402 で JPYC を扱えるようにするための修正が OSS コントリビュートされ、公式リポジトリにマージされたことを知りました。

https://x.com/hyodio/status/1987700077935403010?s=20

その内容は 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が登場することは、以前こちらの記事で紹介しました。
https://zenn.dev/komlock_lab/articles/270e9273f3e7ec

今回は実装を扱うため、その役割をあらためて整理しておきます。

ユーザーの署名を用いて、代わりに送金を実行する仕組み

x402 では、ユーザーがウォレットで「支払いを許可する署名(authorization)」を生成し、
その署名をもとに 第三者が送金を代行するという形式を取ります。

Facilitator はまさにその「第三者」に相当し、
ユーザーの代わりにオンチェーンで送金を実行する役割を持ちます。

送金を実行するのはユーザー本人ではなく、
サーバー側が保持している送信用のウォレット(relayer) であり、
ガス代もサーバー側が負担します。

x402 における位置づけ

x402 は HTTP リクエストに「支払い」という概念を組み込む仕組みですが、
実際のオンチェーン送金はプロトコル外で行われます。

その部分を担うのが Facilitator です。

処理の流れは次のようになります。

  1. クライアントがウォレットで authorization(署名)を生成
  2. クライアント → サーバーへリクエスト
  3. サーバーが /verify API を呼び、署名・支払い内容を検証
  4. 問題なければ /settle API を呼び、Facilitator が送金を実行
  5. 成功したらトランザクション情報を返す

この記事で扱う内容

この記事では、Polygon Mainnet の JPYC を用いて x402 決済を実際に動かすために、
次の 2 点を中心に進めていきます。

  • Facilitator(/verify・/settle)を自前で実装する
  • x402 公式リポジトリに自作 Facilitator を組み込み、Polygon JPYC で x402 が動作することを確認する

自作した Facilitator の挙動と、公式実装(express / MCP)の挙動を比較しながら、
x402 における支払いフロー全体を理解することを目的としています。

環境構築

セットアップ方法はGitHubにまとめています👇

https://github.com/br-to/jpyc-x402-facilitator

ここでは、重要なところだけをピックアップして紹介します。

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 を利用します。

https://www.alchemy.com/

エンドポイントの取得手順は以下の通りです。

  1. Alchemy にログイン(アカウントがなければ無料で作成できます)

  2. Create new app」から新規アプリを作成

  3. Polygon POS を選択

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

Facilitatorの実装

x402 Facilitator の公式仕様

今回のFacilitatorは、Coinbaseのx402プロトコルに準拠します。

公式ドキュメントはこちら

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コントラクトのインスタンスを作成します。

src/common.ts

export const jpycContract = getContract({
  address: process.env.JPYC_CONTRACT_ADDRESS as `0x${string}`,
  abi: jpycAbi,
  client: {
    public: publicClient,
    wallet: walletClient,
  },
});

/verify の実装(verifyService.ts)

/verifyエンドポイントでは、以下のチェックを行います。

src/verifyService.ts

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エンドポイントでは、ブロックチェーン上でトランザクションを実行します。

src/settleService.ts

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のエンドポイントを実装します。

src/server.ts

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にデプロイしましたが、手順としては非常にシンプルです。

  1. リポジトリを Vercel に接続

GitHub と連携し、プロジェクトを選択するだけでデプロイ準備が整います。

  1. 環境変数を設定

Vercel の Dashboard から、.envと同じ項目を設定します。

  • RPC_URL
  • RELAYER_PK
  • JPYC_CONTRACT_ADDRESS
  • CHAIN_ID
  1. デプロイされたらエンドポイントが利用可能に

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 で動作確認

  1. Express サーバーを起動(別のターミナルで)
cd examples/typescript/servers/express
pnpm dev
  1. Cursor を再起動

MCP サーバーが自動的に起動します。

  1. Cursor で天気情報を取得

Cursor のチャットで以下のように依頼します。

天気情報を取得してください

Cursor が get-data-from-resource-server ツールを自動的に呼び出し、x402 決済を通じて天気情報を取得します。

このとき、裏側では以下の処理が行われています。

  1. MCP サーバーが /weather エンドポイントにリクエスト
  2. 402 Payment Required レスポンスを受け取る
  3. EIP-712 署名を自動生成
  4. X-PAYMENT ヘッダーに署名を付与して再リクエスト
  5. サーバーが Facilitator で検証・決済を実行
  6. 天気情報を取得して Cursor に返す

まとめ

この記事では、Polygon Mainnet の JPYC を使った x402 決済の実装を、以下の流れで解説しました。

  1. Facilitator の自作(/verify・/settle の実装とデプロイ)
  2. x402-express で天気 API サーバーを構築(公式 example を活用)
  3. 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 を使っていく未来」を示唆する投稿をされています。

https://x.com/noritaka_okabe/status/1981849360917991885

今回の検証は、JPYC を AI エージェントで扱う場合の一つの動作例として機能します。

また、
https://www.x402scan.com/

ではすでに x402 を使ったプロダクトが次々と登場しており、
AI × マイクロペイメントの流れは今後ますます強まっていくはずです。

記事が参考になった方は、ぜひ Zenn のフォローや
X(@brto_0224) のフォローもよろしくお願いします! 🙌

参考資料

https://github.com/coinbase/x402
https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator
https://www.x402scan.com/

Komlock lab

Discussion

HarukiHaruki

凄!!参考になりました!!