AI Agent之间可以通过x402协议实现服务发现、定价协商和链上结算,全程跑在标准HTTP/TLS上,无需新建传输层,用USDC在Base链结算。
自主代理已经成为可组合的构建块:视觉模型可以调用语言模型,数据获取器可以将结果交给规划器,强化学习控制器可以查询模拟器。开发者无需将每个依赖都硬编码,而是将能力暴露为付费服务,让其他代理在运行时发现、协商和使用这些服务。
一个 marketplace 完成三件事:
发现(Discovery) – 代理发布机器可读的描述符(服务名、版本、输入/输出 schema、价格、SLA)。
协商与结算(Negotiation & Settlement) – 一种轻量级协议约定价格,以可编程 token 支付并记录交易。
调用(Invocation) – 消费方代理使用约定的载荷调用提供方的端点。
2026 年的设计目标是:最小化信任、低延迟、透明成本。参考实现使用 x402 协议(基于 HTTP 的 token _gate RPC)和 Base L2 上的 USDC 进行结算。
x402 扩展了 HTTP 402 Payment Required。当消费方代理请求受保护资源时,提供方返回:
402 Payment Required
X-Pay-Request: {
"amount": "0.05",
"currency": "USDC",
"chain": "base",
"receiver": "0xProvider…",
"nonce": "a1b2c3…",
"expires": 1735689600
}
验证 nonce 未被使用过(重放保护)。
签署一笔支付交易(ERC-20 转账),将确切金额转给接收方。
在重试时将已签署的交易包含在 X-Pay-Response 请求头中。
如果提供方验证通过支付,则返回 200 OK 及服务载荷。
无状态:无需服务端会话。
可基于普通 HTTP/S 运行,现有的 API 网关、CDN 和 sidecar 可以复用。
支付验证是交易收据的纯函数,提供方可以将验证工作外包给验证合约或 rollup 节点。
权衡:消费方必须持有已充值的钱包并处理交易签名延迟(在 Base 上约 200-500 ms)。对于超低延迟循环(< 50 ms),你可能需要预先资助一个托管通道或接受更高的支付失败风险。
提供方在 /.well-known/agent-service 发布一个 JSON 描述符。以下是文本摘要服务的示例:
{
"service": "summarizer-v1",
"version": "1.0.0",
"endpoint": "https://agent.summarizer.example.com/v1/summarize",
"inputSchema": {
"type": "object",
"properties": {
"text": { "type": "string", "maxLength": 8000 }
},
"required": ["text"]
},
"outputSchema": {
"type": "object",
"properties": {
"summary": { "type": "string" }
},
"required": ["summary"]
},
"price": "0.02",
"currency": "USDC",
"chain": "base",
"sla": {
"latencyMs": 500,
"uptimePercent": 99.9
}
}
消费方缓存此描述符(使用 ETag/If-None-Match)以避免重复查询。描述符在给定版本下是不可变的;更新需要新的版本字符串。
以下是一个最小的、生产可用的片段。假设你有一个存储在环境变量中的钱包私钥(PROVIDER_KEY)和指向 Base 的 Web3 提供方。
# provider.py
import os, json, time, hashlib
from flask import Flask, request, abort, Response
from web3 import Web3
from eth_account import Account
app = Flask(__name__)
w3 = Web3(Web3.HTTPProvider(os.getenv("BASE_RPC", "https://base.llamarpc.com")))
acct = Account.from_key(os.getenv("PROVIDER_KEY"))
SERVICE_PRICE = Web3.to_wei(0.02, "ether") # USDC has 6 decimals; adjust if using ERC‑20 directly
NONCE_STORE = set() # in‑memory; replace with Redis for multi‑instance
def verify_payment(req_headers):
"""Check X-Pay-Response for a valid USDC transfer."""
pay_header = req_headers.get("X-Pay-Response")
if not pay_header:
return False
try:
pay = json.loads(pay_header)
tx_hash = pay["txHash"]
receipt = w3.eth.get_transaction_receipt(tx_hash)
if receipt.status != 1:
return False
# Ensure the transfer matches our expectations
tx = w3.eth.get_transaction(tx_hash)
if tx["to"].lower() != acct.address.lower():
return False
if tx["value"] != SERVICE_PRICE:
return False
# Replay protection
nonce = pay.get("nonce")
if nonce in NONCE_STORE:
return False
NONCE_STORE.add(nonce)
return True
except Exception:
return False
@app.route("/v1/summarize", methods=["POST"])
def summarize():
# Payment required header if no valid payment yet
if not verify_payment(request.headers):
nonce = os.urandom(16).hex()
pay_req = {
"amount": Web3.from_wei(SERVICE_PRICE, "ether"),
"currency": "USDC",
"chain": "base",
"receiver": acct.address,
"nonce": nonce,
"expires": int(time.time()) + 300
}
resp = Response(
json.dumps({"error": "payment required"}),
status=402,
mimetype="application/json"
)
resp.headers["X-Pay-Request"] = json.dumps(pay_req)
return resp
# ----- Service logic -----
data = request.get_json(force=True)
text = data.get("text", "")
if not isinstance(text, str) or len(text) > 8000:
abort(400, "Invalid input")
# Dummy summarization: return first 120 chars
summary = text[:120] + ("…" if len(text) > 120 else "")
return Response(
json.dumps({"summary": summary}),
mimetype="application/json"
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
提供方除了短期存在的 nonce 集合(重放保护)外,不存储任何支付状态。
为简单起见价格以 wei 编码;如果使用 ERC-20 合约则必须调用 transfer 而不是原生 ether。
服务逻辑在支付验证之后隔离,使得替换真实模型(例如 HuggingFace 推理调用)变得容易。
消费方遵循相同的流程:获取描述符、尝试调用、处理 402、签名并重发。
// consumer.ts
import { ethers } from "ethers";
import fetch from "node-fetch";
const provider = new ethers.JsonRpcProvider(process.env.BASE_RPC!);
const wallet = new ethers.Wallet(process.env.CONSUMER_KEY!, provider);
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base USDC
const usdc = new ethers.Contract(
USDC_ADDRESS,
["function transfer(address to, uint256 amount) returns (bool)"],
wallet
);
async function fetchDescriptor(url: string) {
const res = await fetch(`${url}/.well-known/agent-service`);
if (!res.ok) throw new Error(`Descriptor fetch failed: ${res.status}`);
return res.json();
}
async function callAgent(endpoint: string, payload: any): Promise<any> {
let attempt = 0;
while (true) {
attempt++;
const res = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
if (res.status === 402 && attempt === 1) {
const payReq = JSON.parse(res.headers.get("x-pay-request") || "{}");
// Build and sign payment
const tx = await usdc.transfer(
payReq.receiver,
ethers.parseUnits(payReq.amount, 6) // USDC has 6 decimals
);
await tx.wait();
// retry with proof
const payResp = {
txHash: tx.hash,
nonce: payReq.nonce,
};
const res2 = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Pay-Response": JSON.stringify(payResp),
},
body: JSON.stringify(payload),
});
if (!res2.ok) throw new Error(`Payment accepted but service failed: ${res2.status}`);
return res2.json();
}
// If we get here, either not 402 or retry failed
const txt = await res.text();
throw new Error(`Agent call failed: ${res.status} ${txt}`);
}
}
// Example usage
(async () => {
const desc = await fetchDescriptor("https://agent.summarizer.example.com");
const result = await callAgent(desc.endpoint, { text: "Long text to summarize..." });
console.log(result.summary);
})();