【ハンズオン】Solana × x402でAPI課金を実装 - ゼロから学ぶ次世代決済プロトコル
こんにちは!ブロックチェーンエンジニアの山口夏生です。
ブロックチェーン×AI Agentで自律経済圏を創るブロックチェーン開発組織Komlock labでCTOをしています。
皆さんは、x402をご存知ですか?
Coinbaseが主導する、暗号資産を利用したAIエージェント時代の決済プロトコルです。
今世界中で、急速に注目を集めています。
(0xJeff氏の投稿を一部翻訳)
エージェント間商取引額: 過去30日間で5,000万ドル(約75億円)に到達。
サービス提供者: フロー(取引の流れ)の95〜99%をサービスプロバイダー(AIエージェント)が占めている。
急成長: 主要な指標は、過去3ヶ月間で3倍以上に増加。
本記事では、Solanaを使ってx402プロトコルをゼロから実装し、その仕組みを基礎から理解します。ライブラリに頼らず仕組みを理解したい方に向けた入門記事です。必要最小限のコードで、ステップごとに解説します。
TL;DR
- x402 = HTTP 402を活用したAPI課金プロトコル
- Solana devnetでサーバー・クライアントをゼロから実装
- 支払いフロー(402→トランザクション署名→検証→決済)を手を動かして理解
x402とは(30秒で理解)
HTTP 402の復活
HTTP 402 Payment Required。1999年にHTTP/1.1で定義されながら、20年以上「将来の使用のため予約」として放置されていたステータスコードです。
それがついに実用化されました。理由は2つ:
- ブロックチェーンの成熟: Solanaでは1トランザクション約$0.00025、確定まで400msと、マイクロペイメントに最適な環境が整った
- AIエージェントの台頭: 2025年にはAIエージェント市場が470億ドル規模に成長すると予測され、人間の介入なしに自動決済するニーズが高まっている
x402プロトコルの概要
x402は、オープンな決済プロトコルです。
HTTP 402ステータスコードを活用し、APIアクセスに対するマイクロペイメントを実現します。
5つのゼロ:
| 特徴 | 説明 |
|---|---|
| ゼロ手数料 | プロトコル手数料なし(ネットワーク手数料のみ) |
| ゼロ待機 | インターネット速度での即座な送金 |
| ゼロ摩擦 | アカウント登録・個人情報不要 |
| ゼロ中央集権 | オープン標準、誰でも構築・拡張可能 |
| ゼロ制限 | 特定のネットワークに非依存 |
x402のユースケース
x402が活用できる代表的なシナリオを紹介します。
| ユースケース | 説明 |
|---|---|
| 有料API | AI推論、データ分析、翻訳などのAPIを従量課金で提供 |
| プレミアムコンテンツ | 記事、動画、音楽への単発アクセス課金 |
| AIエージェント間決済 | エージェントが自律的に他のサービスを購入 |
| IoTマイクロペイメント | センサーデータやコンピューティングリソースの売買 |
| ゲーム内課金 | アイテムや機能のリアルタイム購入 |
特にAIエージェント連携は注目分野で、MCP(Model Context Protocol)サーバーにx402を組み込むことで、Claude等のAIアシスタントが自動的に有料APIにアクセスできるようになります。
x402 on Solana
x402はネットワーク非依存のプロトコルで、Base、Arbitrum、Solanaなど複数のチェーンに対応しています。
本記事ではSolanaを選択します。
Solanaの特徴:
- 低コスト: 1トランザクション約$0.00025
- 高速: 確定まで約400ms
- USDC対応: Circle公式のdevnet Faucetあり
先日、Solana上でのx402の取引ボリュームが、x402主要チェーンであるBASEを抜きました🔥
周辺技術やサービス
x402エコシステムには、以下のツールやサービスがあります。
| ツール/サービス | 説明 |
|---|---|
| x402-solana | Solana用のx402SDK by PayAI。サーバー/クライアント両対応 |
| CDP Facilitator | Coinbase提供のファシリテーター |
| Corbits | x402を活用したAIエージェント決済プラットフォーム |
参考動画: Corbidsの仕組みについては、Yukiさんの解説動画が詳しいです。
x402の実行フロー
標準的なx402フロー(verify → settle)
x402の標準的な支払いフローでは、verify(検証) と settle(決済) の2段階で処理されます。
ポイント:
- verify: 支払いが有効かどうかを検証(まだ決済しない)
- settle: コンテンツ提供後に実際の決済を実行
- Facilitator(CDP等)が検証・決済を仲介
X-Payment ヘッダーの構造(v1)
クライアントは署名済みトランザクションを以下の形式でサーバーに送ります:
{
"x402Version": 1,
"scheme": "exact",
"network": "solana-devnet",
"payload": {
"serializedTransaction": "<base64エンコードされたトランザクション>"
}
}
この JSON 全体をBase64エンコードして X-Payment ヘッダーに格納します。
x402のバージョンについて
本記事は x402 V1 仕様で解説しています。2025年後半にリリースされたV2では、ヘッダー名が X-Payment から PAYMENT-SIGNATURE 等に変更され、ネットワーク指定もCAIP-2形式(solana:5eykt4U...)に変わっています。
本記事でV1を採用する理由:
- Solana公式のx402ガイドがV1ベースで解説されている
- Facilitatorを使わないゼロから実装では、V1/V2の差異は影響しない
- 「クライアントが署名→サーバーが検証・送信」という基本フローはV1/V2で共通
- 学習目的ではシンプルなV1形式の方が理解しやすい
本番環境でFacilitatorを使う場合は、公式ドキュメントで最新仕様を確認してください。
ゼロから実装でx402を体験
ここからは実際にコードを書いて、x402の仕組みを体験します。
本ハンズオンは Solana公式のx402ガイド を参考に、ステップごとに丁寧に解説したものです。
実際に手元で動かしてみたい方は、こちらのリポジトリをCloneしてください。
実装するフローの全体像
これから実装するx402の支払いフローを先に確認しておきましょう。
-
クライアント →
/premiumにリクエスト -
サーバー →
402 Payment Requiredで支払い条件(宛先・金額)を返却 - クライアント → 支払いトランザクションを作成・署名
-
クライアント →
/premiumに再リクエスト(トランザクションを添付) - サーバー → トランザクションを検証し、ネットワークに送信
-
サーバー → 確認後、
200 OKでコンテンツを返却
前提条件
- Node.js v20以上
- Solana CLI
1. プロジェクトのセットアップ
# リポジトリをクローン
git clone https://github.com/natsukingly/x402-solana-demo.git
cd x402-solana-demo
# 依存関係をインストール
npm install
ファイル構成
x402-solana-demo/
├── server.ts # サーバー(ハンズオン用テンプレート)
├── client.ts # クライアント(ハンズオン用テンプレート)
├── server.sample.ts # サーバー完成版
├── client.sample.ts # クライアント完成版
├── server.json # サーバーウォレット(要作成)
├── client.json # クライアントウォレット(要作成)
├── .env # 環境変数(要作成)
└── .env.example # 環境変数テンプレート
-
server.ts/client.ts: TODOコメントを埋めていくハンズオン形式 -
*.sample.ts: 困ったときの解答
2. Solana CLI設定(devnet)
# Solana CLIがなければインストール
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
# PATHに追加
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
# devnetに接続
solana config set --url devnet
# 新しいウォレット作成(サーバー用)
solana-keygen new --outfile server.json
# クライアント用ウォレットも作成
solana-keygen new --outfile client.json
環境変数の設定
# .env.exampleをコピー
cp .env.example .env
# サーバーウォレットの公開鍵を確認
solana-keygen pubkey server.json
# .envのRECIPIENT_WALLETを更新
RECIPIENT_WALLET=上記で表示された公開鍵
devnet SOLの取得
# サーバーにSOLをエアドロップ
solana airdrop 2 $(solana-keygen pubkey server.json)
# クライアントにSOLをエアドロップ
solana airdrop 2 $(solana-keygen pubkey client.json)
レートリミットエラーが出る場合:
- Solana Faucet からブラウザで取得
- または数分待ってから再試行
3. devnet USDCの取得
devnetではテスト用のUSDCを使用します。Faucetから取得できます:
上記サイトでSolana Devnetを選択し、クライアントウォレットのアドレスを入力してUSDCを取得してください。

Circle FaucetでSolana Devnetを選択した状態
4. サーバー用Token Accountの作成
サーバーがUSDCを受け取るためのToken Accountを事前に作成しておきます。
spl-token create-account 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU \
--owner $(solana-keygen pubkey server.json) \
--fee-payer server.json
ハンズオン: サーバー実装
server.ts を開いて、各TODOセクションのコードを実装していきましょう。
編集前のserver.ts
Step 1: 定数を設定
以下の4つの定数を定義してください。
| 変数名 | 内容 | ヒント |
|---|---|---|
connection |
Solana Devnetへの接続 | new Connection("https://api.devnet.solana.com") |
USDC_MINT |
Devnet USDCのMintアドレス | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU |
RECIPIENT_WALLET |
サーバーウォレットの公開鍵 |
solana-keygen pubkey server.json で確認 |
PRICE_USDC |
価格(6桁小数点) |
100 = 0.0001 USDC |
正解
const connection = new Connection("https://api.devnet.solana.com");
const USDC_MINT = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
const RECIPIENT_WALLET = new PublicKey(process.env.RECIPIENT_WALLET!);
const PRICE_USDC = 100; // 0.0001 USDC
Step 2: X-Paymentヘッダーをデコード
クライアントから送られてきたX-Paymentヘッダーをデコードします。
やること:
-
xPaymentHeaderを Base64デコードしてJSON.parse -
paymentData.payload.serializedTransactionを Base64デコード -
Transaction.from()でトランザクションオブジェクトに変換 -
getAssociatedTokenAddress(USDC_MINT, RECIPIENT_WALLET)で受取先Token Account計算
正解
const paymentData = JSON.parse(
Buffer.from(xPaymentHeader, "base64").toString("utf-8")
);
const txBuffer = Buffer.from(
paymentData.payload.serializedTransaction,
"base64"
);
const tx = Transaction.from(txBuffer);
const recipientTokenAccount = await getAssociatedTokenAddress(
USDC_MINT,
RECIPIENT_WALLET
);
Step 3: インストラクション検証
トランザクション内のSPL Token Transfer Txを検証します。これがx402の最も重要な部分です。
検証のポイント:
| チェック項目 | 方法 |
|---|---|
| SPL Token Txか | ix.programId.equals(TOKEN_PROGRAM_ID) |
| Transfer Txか |
ix.data[0] === 3(opcode 3 = Transfer) |
| 金額の読み取り |
ix.data.readBigUInt64LE(1)(u64 little-endian) |
| 宛先の確認 |
ix.keys[1].pubkeyが自分のToken Accountか |
正解
let validTransfer = false;
let transferAmount = 0;
for (const ix of tx.instructions) {
if (ix.programId.equals(TOKEN_PROGRAM_ID)) {
if (ix.data.length >= 9 && ix.data[0] === 3) {
transferAmount = Number(ix.data.readBigUInt64LE(1));
if (ix.keys.length >= 2) {
const destAccount = ix.keys[1].pubkey;
if (
destAccount.equals(recipientTokenAccount) &&
transferAmount >= PRICE_USDC
) {
validTransfer = true;
break;
}
}
}
}
}
if (!validTransfer) {
return res.status(402).json({
error: "Invalid transfer instruction",
details: "Transfer destination or amount is incorrect",
});
}
Step 4: トランザクション処理
検証に通過したトランザクションをネットワークに送信します。
やること:
-
connection.simulateTransaction(tx)でシミュレート -
connection.sendRawTransaction(txBuffer, { skipPreflight: false })で送信 -
connection.confirmTransaction(signature, "confirmed")で確認
正解
const simulation = await connection.simulateTransaction(tx);
if (simulation.value.err) {
return res.status(402).json({
error: "Simulation failed",
details: simulation.value.err,
});
}
const signature = await connection.sendRawTransaction(txBuffer, {
skipPreflight: false,
});
const confirmation = await connection.confirmTransaction(
signature,
"confirmed"
);
if (confirmation.value.err) {
return res.status(402).json({ error: "Transaction failed on-chain" });
}
return res.json({
data: "これはプレミアムコンテンツです!支払いありがとうございます。",
paymentDetails: {
signature,
amount: transferAmount,
amountUSDC: transferAmount / 1_000_000,
explorerUrl: `https://explorer.solana.com/tx/${signature}?cluster=devnet`,
},
});
Step 5: 402レスポンス
支払いがない場合、status(402) で支払い情報を返します。
{
message: "Payment Required",
payment: { recipientWallet, tokenAccount, mint, amount, amountUSDC }
}
正解
const recipientTokenAccount = await getAssociatedTokenAddress(
USDC_MINT,
RECIPIENT_WALLET
);
return res.status(402).json({
message: "Payment Required",
payment: {
recipientWallet: RECIPIENT_WALLET.toBase58(),
tokenAccount: recipientTokenAccount.toBase58(),
mint: USDC_MINT.toBase58(),
amount: PRICE_USDC,
amountUSDC: PRICE_USDC / 1_000_000,
},
});
Step 6: サーバー起動
app.listen(3001, ...) でポート3001で起動します。
正解
app.listen(3001, () => {
console.log("x402 Server running on http://localhost:3001");
});
ハンズオン: クライアント実装
client.ts を開いて、各TODOセクションのコードを実装していきましょう。
編集前のclient.ts
Step 1: 接続とウォレット設定
| 変数名 | 内容 | ヒント |
|---|---|---|
connection |
Solana Devnetへの接続 | new Connection("https://api.devnet.solana.com") |
payer |
クライアントウォレット |
client.json から Keypair.fromSecretKey() で作成 |
正解
const connection = new Connection("https://api.devnet.solana.com");
const keypairData = JSON.parse(readFileSync("./client.json", "utf-8"));
const payer = Keypair.fromSecretKey(Uint8Array.from(keypairData));
Step 2: サーバーから支払い情報を取得
やること:
-
fetch("http://localhost:3001/premium")でリクエスト -
response.json()でJSONパース(as PaymentQuoteでキャスト) -
status !== 402なら早期リターン
正解
const quoteResponse = await fetch("http://localhost:3001/premium");
const quote = (await quoteResponse.json()) as PaymentQuote;
if (quoteResponse.status !== 402) {
console.log("Unexpected response:", quote);
return;
}
console.log(` Required: ${quote.payment.amountUSDC} USDC`);
console.log(` Recipient: ${quote.payment.tokenAccount}`);
Step 3: 支払い情報を変数に格納
402レスポンスの quote.payment から必要な情報を取り出します。
| 変数名 | 取得元 |
|---|---|
recipientTokenAccount |
quote.payment.tokenAccount |
mint |
quote.payment.mint |
amount |
quote.payment.amount |
正解
const recipientTokenAccount = new PublicKey(quote.payment.tokenAccount);
const mint = new PublicKey(quote.payment.mint);
const amount = quote.payment.amount;
Step 4: 支払い元Token Accountを取得
残高チェックも実装(payerTokenAccount.amount を使用)
正解
const payerTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
payer.publicKey
);
console.log(` Payer Token Account: ${payerTokenAccount.address.toBase58()}`);
console.log(` Balance: ${Number(payerTokenAccount.amount) / 1_000_000} USDC`);
if (Number(payerTokenAccount.amount) < amount) {
console.error("Insufficient USDC balance!");
console.log("Get devnet USDC from: https://faucet.circle.com/");
return;
}
Step 5: USDC転送Txを作成
createTransferInstruction でSPL Token転送Txを作成します。
| 引数 | 説明 |
|---|---|
source |
送信元Token Account(payerTokenAccount.address) |
destination |
送信先Token Account(recipientTokenAccount) |
owner |
署名者(payer.publicKey) |
amount |
転送金額 |
正解
const transferIx = createTransferInstruction(
payerTokenAccount.address, // source
recipientTokenAccount, // destination
payer.publicKey, // owner
amount // amount
);
Step 6: トランザクション構築と署名
やること:
-
connection.getLatestBlockhash()でブロックハッシュ取得 new Transaction({ feePayer, blockhash, lastValidBlockHeight })-
tx.add(transferIx)でTxを追加 -
tx.sign(payer)で署名(送信はしない!)
正解
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
const tx = new Transaction({
feePayer: payer.publicKey,
blockhash,
lastValidBlockHeight,
});
tx.add(transferIx);
tx.sign(payer);
Step 7: x402ペイロード作成
署名済みトランザクションをx402形式のペイロードに変換します。
やること:
-
tx.serialize().toString("base64")でトランザクションをBase64化 - x402形式のオブジェクトを作成(
x402Version,scheme,network,payload) - オブジェクト全体をJSON → Base64に変換して
X-Paymentヘッダー用の文字列を作成
正解
const serializedTx = tx.serialize().toString("base64");
const paymentProof = {
x402Version: 1,
scheme: "exact",
network: "solana-devnet",
payload: {
serializedTransaction: serializedTx,
},
};
const xPaymentHeader = Buffer.from(
JSON.stringify(paymentProof)
).toString("base64");
Step 8: X-Paymentヘッダー付きでリクエスト
-
status === 200なら成功、それ以外は失敗
正解
console.log("\n3. Sending payment...");
const paidResponse = await fetch("http://localhost:3001/premium", {
headers: {
"X-Payment": xPaymentHeader,
},
});
const result = (await paidResponse.json()) as PaymentResult;
if (paidResponse.status === 200) {
console.log("\n=== Payment Successful! ===");
console.log(`Content: ${result.data}`);
console.log(`Transaction: ${result.paymentDetails?.explorerUrl}`);
} else {
console.log("\n=== Payment Failed ===");
console.log(result);
}
完成後のコード
server.ts
client.ts
動作確認
サーバーの起動
npx ts-node server.ts
出力:
x402 Server running on http://localhost:3001
クライアントの実行
別のターミナルで:
npx ts-node client.ts
成功時の出力例
=== x402 Client Demo ===
Payer: Fz7qLnp9qVNTofMBnft3ZKKmVzMwuiAMg8Sf49rjNkqt
1. Requesting payment quote...
Required: 0.0001 USDC
Recipient: 3czoXAXJ9biX2VVLP3oigVcg7stuRxWhJz8be9BkWP5W
2. Creating payment transaction...
Payer Token Account: 5gtyiVdUhUND9Y9Dt7UbT9GBhmeiMBUh66FSBgVA9kXj
Balance: 1 USDC
3. Sending payment...
=== Payment Successful! ===
Content: これはプレミアムコンテンツです!支払いありがとうございます。
Transaction: https://explorer.solana.com/tx/xxxxx?cluster=devnet
Solana Explorerでトランザクションを確認すると、USDCの転送が記録されているのがわかります。

Solana Explorer(Devnet)でトランザクションを確認できます
トラブルシューティング
"Non-base58 character" エラー:
→ .env の RECIPIENT_WALLET が正しいSolana公開鍵になっているか確認
"InvalidAccountData" エラー:
→ サーバー用Token Accountが作成されているか確認(手順4参照)
spl-token create-account 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU \
--owner $(solana-keygen pubkey server.json) \
--fee-payer server.json
"Insufficient USDC balance" エラー:
→ Circle Faucet からクライアントウォレットにUSDCを追加取得
devnet SOL airdropがレートリミット:
→ Solana Faucet からブラウザで取得、または数分待って再試行
blockhash期限切れエラー:
→ blockhashの有効期限は約60秒。再試行する場合は取り直す
おまけ: Solana固有の代替フロー
本記事では x402.org 標準フロー(サーバーがトランザクションを送信)を実装しましたが、Solanaでは別のアプローチも可能です。
代替フロー: クライアントが直接送信
- クライアントがトランザクションを直接ネットワークに送信(memo Tx付き)
- クライアントはトランザクション署名だけをサーバーに送信
- サーバーは署名を検証してコンテンツを返却
メリット:
- 接続断の問題を解決: 支払い後にクライアントが切断しても、同じ署名で再リクエスト可能(支払いは既にオンチェーンで確定済み)
デメリット:
- x402.org標準フローから逸脱する(標準ではサーバーがトランザクションを送信)
本番環境で接続断が問題になる場合は、この代替フローも検討してみてください。
応用: AIエージェント連携
なぜx402はAIエージェントと相性が良いのか
- 自動決済: 人間の介入なしにAPIアクセス料金を支払える
- マイクロペイメント: 1回のAPI呼び出しに$0.001のような少額課金が可能
- オープン標準: どのAIエージェントフレームワークからでも利用可能
MCP(Model Context Protocol)サーバーへの応用
Claude DesktopなどのMCPクライアントから利用するサーバーにx402を組み込むことで、AIアシスタントが自動的に有料APIにアクセスできます。
User → Claude Desktop → MCP Server (x402) → Paid API
↓
支払いを自動実行
関連プロジェクト
| プロジェクト | 説明 |
|---|---|
| Coinbase AgentKit | AIエージェント向けブロックチェーンツールキット |
| MCPay.tech | MCPサーバーのマネタイズ支援 |
| Google A2A | エージェント間通信・決済プロトコル(開発中) |
| Agents Payment Protocol (AP2) | Google主導のエージェント決済標準化 |
まとめ
本記事では、x402プロトコルをライブラリなしで実装し、その仕組みを解説しました。
学んだこと:
- HTTP 402ステータスコードを活用した課金フロー
- SPL Token Transfer Txの検証方法
- X-Paymentヘッダーの構造
本番で使うなら
本記事のゼロから実装は学習目的です。本番環境ではFacilitatorの利用を推奨します。
Facilitatorを使う理由:
- サーバー側でブロックチェーンインフラを維持する必要がない
- verify(検証)とsettle(決済)を分離し、コンテンツ提供後に決済を実行できる
- Facilitatorは署名されたトランザクションを実行するだけで、支払い内容の改ざんや資金の窃取はできない設計
Coinbaseが提供しているsolana向けのパッケージの例です。
TypeScript以外にも様々な言語で提供されています。
参考リンク
x402公式:
- x402.org - 公式サイト
- x402 Whitepaper - 技術仕様
- Coinbase x402 GitHub - リファレンス実装
Solana関連:
- Solana x402ガイド - 公式チュートリアル
日本語解説:
- x402プロトコルとは? - x402の概要と背景を日本語で解説
最後まで読んでいただきありがとうございました!
この記事が参考になったら、ぜひいいねやコメントをお願いします。
Komlock labはAI x ブロックチェーン開発に注力しています!!
Komlock labでは、こうした最新規格の調査だけでなく、実際のプロダクト開発や技術検証に日々励んでいます。
こちらは、以前開発したAIエージェント間の業務委託契約の紹介記事です。
Komlock lab エンジニア募集中
「AI x ブロックチェーン」という未開拓な領域で、未来のインフラを一緒に作りませんか?
エンジニア絶賛募集中ですので、DMでもDiscordでもお気軽にご連絡ください!
DM宛先:
PR記事とCEOの創業ブログ
Komlock lab もくもく会&LT会
ブロックチェーン開発関連のイベントを定期開催しています!
Discordコミュニティ
有益な記事の共有や開発の相談など行っています。どなたでもウェルカムです
Discussion