学習メモ — x402(HTTP 402 決済プロトコル)¶
lectures/x402_basics/ の写経で得た理解を1枚に圧縮したメモ。混乱はほぼ「要件はどこに乗るのか(ボディ)」「支払いはどこに乗るのか(ヘッダ)」「署名は何を縛るのか」の3点のすれ違いで起きるので、後半の用語集を最初に押さえると速い。
1行で言うと¶
x402 = HTTP 402 Payment Required に「払い方」をボディで返し、クライアントが署名した「支払い」をヘッダ(X-PAYMENT)に詰めて送り直す、それだけの約束事。 決済網でも新チェーンでもない。402 → X-PAYMENT → 200 の2往復と、USDC の EIP-3009 署名(exact スキーム)を掴めば全部見える。
何が嬉しいのか(直感)¶
従来の「API にお金を払う」は、人間が事前にアカウントを作り、カードを登録し、API キーを発行して…という前もっての契約が要る。x402 はこれを その場・都度・無登録 にする。
x402 = 自動販売機。 アカウントもカードも要らず、「これ欲しい(GET)」→「◯◯円です(402)」→「はい(X-PAYMENT)」→「どうぞ(200)」で完結する。違いは、硬貨の代わりに USDC の署名済み送金許可 を投入する点だけ。
だから AI エージェントが自分の判断で API/データにその場で課金できる。「エージェントが金を払う」の下回りがこれ。
登場人物は3人(役割の分離が肝)¶
| 役者 | 正体 | 何をする |
|---|---|---|
| クライアント(買い手) | 人間 or AI エージェント + ウォレット | 402 を受けて要件を読み、署名して X-PAYMENT を付けて再送。鎖には触らない |
| リソースサーバ(売り手) | 課金したい API/サイト | 未払いなら 402 +要件を返す。支払いが来たら facilitator に検証/決済を委譲。鎖の知識ゼロでよい |
| facilitator(決済代行) | Coinbase CDP 等の HTTP サービス | 署名を検証(/verify)し、オンチェーン送金を実行(/settle)。ガス代を肩代わりする |
→ 売り手と払い手のどちらも「オンチェーンの難しい部分」を facilitator に押し付けられる。これが普及の肝。ex04 で facilitator を別ポートの独立サービスとして立てて実感する。
ハンドシェイクの流れ(2往復)¶
sequenceDiagram
participant C as クライアント (買い手/エージェント)
participant S as リソースサーバ (売り手)
participant F as facilitator (決済代行)
C->>S: GET /premium-data (支払い無し)
S-->>C: 402 + accepts[] を **ボディ** で(払い方の提示)
Note over C: accepts[0] を読み、払える1件を選ぶ
C->>C: EIP-3009 を EIP-712 署名し PaymentPayload を作る
C->>S: GET /premium-data + X-PAYMENT: base64(payload)
S->>F: POST /verify (payload + requirements)
F-->>S: isValid=true, payer=0x..
S->>F: POST /settle (オンチェーン送金を委譲)
F-->>S: success=true, transaction=0x..
S-->>C: 200 + X-PAYMENT-RESPONSE: base64(receipt) +本文
- 1回目は必ず 402。要件(いくら・どの鎖・どのトークン・誰に)を読んでから払う。
@x402/fetch(本家クライアント)はこの横取り→署名→再送を自動でやる(ex02はそれを手で並べたもの) - 要件はボディ、支払いと結果はヘッダ。ヘッダは文字列しか運べないので、PaymentPayload も SettlementResponse も base64(JSON) で詰める
/verifyと/settleは分かれている。「検証だけ先に」「決済は後で」を分離できる設計
"exact" スキームの中身 — 署名が支払いを縛る¶
EVM の定番スキーム exact は、トークンの EIP-3009 transferWithAuthorization(「この条件で送金してよい」という署名済みの許可)を使う。払う側は鎖に触らず、次の型付きデータ(EIP-712)に署名するだけ:
TransferWithAuthorization {
from : address // 払う人(=署名者)
to : address // 受取先(要件の payTo と一致すべき)
value : uint256 // 金額(atomic units)。exact は要求額ちょうど
validAfter : uint256 // これ以降有効
validBefore : uint256 // これを過ぎたら無効(timeout)
nonce : bytes32 // 使い捨て乱数(リプレイ防止)
}
さらに domain(name, version, chainId, verifyingContract=トークンアドレス)も署名対象。name/version は要件の extra から、chainId は network から導く。つまり 「どのトークンの・どの鎖の・誰から誰へ・いくらを・いつまで」を丸ごと1つの署名が縛る。
facilitator の検証(/verify)はこうなる:
1. 署名から署名者アドレスを復元(recover)し、from と一致するか
2. 受取先 to が要件の payTo と一致するか
3. value が要求額以上か
4. validAfter〜validBefore の期間内か
5. (本物はさらに)残高照会・transferWithAuthorization のシミュレート
なぜ X-PAYMENT を横取りされても盗めないか(ex03 の2経路)¶
| 攻撃 | どうなるか | reason |
|---|---|---|
改ざん①: 署名後に value を 0.01→100 USDC に書き換え |
署名対象が変わり、復元アドレスが from と一致しなくなる |
invalid_exact_evm_payload_signature |
| 改ざん②: 攻撃者宛に正しく署名した支払いを、正規の要件で検証 | 署名は有効(from に復元できる)が、受取先が payTo と不一致 |
invalid_exact_evm_payload_recipient_mismatch |
①は「署名の完全性」、②は「要件との突き合わせ」で弾く別経路。 中間者が金額や受取先を書き換えると①で、そもそも別の相手に払う署名を持ち込むと②で落ちる。これが「確率で防ぐのではなく構造で防ぐ」の教科書例(guardrails_basics の対比と地続き)。
v1 と v2 は別物(混ぜない・最重要の落とし穴)¶
本家リポジトリには 2つの仕様バージョンがあり、ワイヤーフォーマットが違う。このレクチャーは現在稼働中の v1。
| v1(このレクチャー・稼働中) | v2(新・transport-agnostic 再設計) | |
|---|---|---|
| 要件配列 | accepts (複数) |
accepted (単数) + top-level resource |
| 金額フィールド | maxAmountRequired |
amount |
| network 表記 | "base-sepolia"(人間名) |
"eip155:84532"(CAIP-2) |
| スキーム | EIP-3009 のみ | EIP-3009 / Permit2 / ERC-7710 |
| バージョン番号 | x402Version: 1 |
x402Version: 2 |
spec の schemes/exact/scheme_exact_evm.md の例は v2 で書かれている箇所があるので、フィールド名を写すときは v1 の x402-specification-v1.md を見ること。
金額は必ず atomic units の「文字列」¶
maxAmountRequired も authorization.value も、トークンの最小単位(atomic units)の整数を文字列で運ぶ。float にしないのは桁落ち防止(金額の鉄則)。
- USDC は 6 桁小数 →
"10000"= 0.01 USDC、"1000000"= 1.00 USDC _x402.atomic_to_decimal("10000")→"0.010000"(float を経由しない変換)
用語集(混乱の早見表)¶
| 用語 | 一言で | 具体例 / どこで見るか |
|---|---|---|
| x402Version | プロトコルの版。1 と 2 は別物 | 全メッセージの先頭。このレクは 1 |
| PaymentRequirements | 402 ボディの accepts[] に入る1件=「払い方の提示」 |
ex01 で組む。scheme/network/maxAmountRequired/payTo/asset/extra |
| accepts[] | 提示された払い方の配列(複数可) | 402 ボディ。クライアントは払える1件を選ぶ |
| X-PAYMENT | クライアント→サーバのリクエストヘッダ。base64(PaymentPayload) | ex02/ex04 の2往復目 |
| PaymentPayload | X-PAYMENT の中身。{scheme, network, payload:{signature, authorization}} |
ex02 でデコードして観察 |
| authorization | EIP-3009 のパラメータ。from/to/value/validAfter/validBefore/nonce | 署名対象そのもの |
| X-PAYMENT-RESPONSE | サーバ→クライアントのレスポンスヘッダ。base64(SettlementResponse) | 200 と一緒に返る決済レシート |
| SettlementResponse | 決済結果。{success, transaction, network, payer}(失敗時 transaction="" + errorReason) |
/settle の返り=レシート |
| facilitator | 検証(/verify)と決済(/settle)を代行する HTTP サービス |
ex04 で別ポートに立てる。本番は Coinbase CDP |
| exact スキーム | 「ちょうどこの額」を EIP-3009 で送るスキーム | EVM の定番。ex03 |
| EIP-3009 transferWithAuthorization | 署名だけで送金を許可(ガスは他人が払える=gasless) | _exact.py の型定義 |
| EIP-712 | 構造化データに署名する規格。domain + 型 で「何への署名か」を明示 | _exact._typed_data |
| domain | 署名が「どのトークン・どの鎖向けか」を縛る識別子。name/version/chainId/verifyingContract | extra(name,version) + network(chainId) + asset(contract) から組む |
| atomic units | トークンの最小単位の整数(文字列で運ぶ) | USDC "10000" = 0.01 |
| nonce | 使い捨て乱数。同じ支払いの再利用(リプレイ)を防ぐ | authorization.nonce(bytes32) |
| /supported | facilitator が対応する (version, scheme, network) の一覧 | ex04 の GET |
判定に迷ったら¶
- 「これはボディ? ヘッダ?」 → 払い方の提示(要件)はボディ、支払いとレシートはヘッダ(base64)。
- 「verify と settle どっち?」 → 署名と条件が正しいか=
verify、実際にお金を動かす=settle。 - 「reason が signature か recipient_mismatch か」 → 署名後に中身を書き換えた=signature、正しい署名だが払い先が要件と違う=recipient_mismatch。
よくある誤解の訂正¶
- 「x402 は独自の決済ネットワーク」→ ×。ただの HTTP 402 + 2ヘッダの規約。決済の実体は既存のトークン(USDC)と既存の署名規格(EIP-3009/712)。
ex04で「結局ただの HTTP」と分かる。 - 「払う側が鎖にトランザクションを送る」→ ×。払う側は署名するだけ。ブロードキャストとガス代は facilitator。だからエージェントは秘密鍵で署名できさえすればよい。
- 「要件はヘッダで返る」→ ×。要件(accepts)はボディ。ヘッダ名
PAYMENT-REQUIRED等は本家の概念図の抽象名で、実 HTTP の要件はボディに乗る。 - 「maxAmountRequired は数値」→ ×。文字列(atomic units)。JSON の number にすると桁落ちする。
- 「v1 と v2 はほぼ同じ」→ ×。
accepts/accepted、maxAmountRequired/amount、network 表記が違う。写経時は v1 仕様を見る。
困りごと → 見る/使うもの(クイック早見表)¶
| 困りごと | 見る/使うもの |
|---|---|
| 402 ボディに何が乗るか知りたい | ex01 / _x402.PaymentRequirements |
| 2往復の流れを追いたい | ex02 / 本メモのシーケンス図 |
| なぜ偽造できないか知りたい | ex03 の改ざん①②(signature / recipient_mismatch) |
| facilitator の API を叩きたい | ex04(/verify /settle /supported) |
| フィールド名の正確な定義 | 本家 specs/x402-specification-v1.md |
| 金額の atomic 変換 | _x402.atomic_to_decimal |
| v1/v2 で混乱した | 本メモ「v1 と v2 は別物」表 |
作成: 2026-07-02 / 最終更新: 2026-07-02