コンテンツにスキップ

学習メモ — 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   // 使い捨て乱数(リプレイ防止)
}

さらに domainname, version, chainId, verifyingContract=トークンアドレス)も署名対象。name/version は要件の extra から、chainIdnetwork から導く。つまり 「どのトークンの・どの鎖の・誰から誰へ・いくらを・いつまで」を丸ごと1つの署名が縛る

facilitator の検証(/verify)はこうなる: 1. 署名から署名者アドレスを復元(recover)し、from と一致するか 2. 受取先 to が要件の payTo と一致するか 3. value が要求額以上か 4. validAftervalidBefore の期間内か 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 の「文字列」

maxAmountRequiredauthorization.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/acceptedmaxAmountRequired/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