x402将HTTP 402状态码扩展为机器可读的支付请求头,Agent可读取金额和代币信息、签署区块链交易、回传支付证明,全程无需新建传输协议,支持任意EVM链和ERC-20代币。
AI Agent 经常调用微服务:语言模型端点、向量搜索、运行短脚本的工具等。传统发票或 API 密钥分层按调用次数计费会增加运营开销,并迫使 Agent 在线外维护余额。
x402 重新利用现有的 HTTP 402 Payment Required 状态码,在响应头中直接传递机器可读的支付请求。Agent 随后可以:
从 Header 中读取所需金额、代币和网络信息。
签署一笔向服务提供商支付的区块链交易。
带上付款凭证重新发送原始请求。
所有这些都通过普通的 HTTP/TLS 完成,无需新的传输协议。该方案适用于任何 EVM 兼容链(包括 Base)和任何 ERC-20 代币,使其成为基于 USDC 的微支付的理想选择。
当服务器请求付款时,返回 402 状态码,并在 Header 中包含一个 Payment 头,其值是一个 JSON 对象:
{
"scheme": "erc20",
"network": "base",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
"amount": "0.01", // 本次调用所需的确切金额
"maxAmount": "0.10", // 可选上限,防止过度收费
"payee": "0x1111…dead", // 服务提供商的钱包地址
"timeout": 30 // 客户端完成结算的秒数
}
如果客户端已经付款,可以带上包含交易哈希(或签名收据)的 X-Payment Header 重新发送请求。服务器在链上验证哈希,有效则处理请求并返回正常的 200。
这些权衡是现实的;x402 不是银弹,而是在开销可接受时将结算嵌入 HTTP 的务实方式。
下面是两个自包含代码片段:一个 Node.js/Express 服务器,用 x402 保护端点;一个浏览器/Node 兼容的客户端,使用 ethers.js 完成支付和重试。
Base RPC URL: https://base-mainnet.g.alchemy.com/v2/<YOUR_KEY>
Base 上的 USDC 合约: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913(6 位小数)
服务钱包(收款方): 0x1111111111111111111111111111111111111111(替换为你自己的)
Agent 私钥:安全存储在环境变量 AGENT_PRIVKEY 中。
// server.js
import express from 'express';
import { ethers } from 'ethers';
const app = express();
const PORT = 3000;
// Configuration – replace with your own values
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const PAYEE = '0x1111111111111111111111111111111111111111';
const NETWORK = 'base';
const AMOUNT_USDC = '0.01'; // 每次调用 0.01 USDC
const MAX_AMOUNT_USDC = '0.10';
// Helper: format amount as integer units (USDC has 6 decimals)
function toUnits(amountStr) {
return ethers.parseUnits(amountStr, 6);
}
// Middleware that enforces x402 payment
async function requirePayment(req, res, next) {
// Look for proof of payment in X-Payment header
const txHash = req.get('X-Payment');
if (txHash) {
try {
const provider = new ethers.JsonRpcProvider(process.env.BASE_RPC);
const tx = await provider.getTransaction(txHash);
if (!tx) throw new Error('Tx not found');
const receipt = await provider.waitForTransaction(txHash);
if (receipt.status !== 1) throw new Error('Tx failed');
// Basic checks: correct token, recipient, amount
if (tx.to?.toLowerCase() !== USDC_ADDRESS.toLowerCase())
throw new Error('Wrong token contract');
// For ERC-20 transfer we need to inspect input data; here we assume a simple transfer
// In production you'd decode the ERC-20 Transfer event from receipt logs.
// For brevity we skip full validation – see note below.
} catch (e) {
return res.status(402).set('Payment', JSON.stringify({
scheme: 'erc20',
network: NETWORK,
asset: USDC_ADDRESS,
amount: AMOUNT_USDC,
maxAmount: MAX_AMOUNT_USDC,
payee: PAYEE,
timeout: 30,
}));
}
return next();
}
// No payment proof – challenge the client
res.status(402).set('Payment', JSON.stringify({
scheme: 'erc20',
network: NETWORK,
asset: USDC_ADDRESS,
amount: AMOUNT_USDC,
maxAmount: MAX_AMOUNT_USDC,
payee: PAYEE,
timeout: 30,
}));
}
app.use(requirePayment);
app.get('/api/data', (req, res) => {
res.json({ message: 'Here is the paid data!', ts: Date.now() });
});
app.listen(PORT, () => console.log(`Server listening on ${PORT}`));