从编码工具发起支付
下面的示例展示如何为 AI 编码工具加入 x402 支付处理能力。当工具收到 402 响应时,会自动付款并重试。
两个示例都需要:
- 一个钱包私钥(通过环境变量
X402_PRIVATE_KEY设置) - x402 相关包:
@x402/fetch、@x402/evm和viem
OpenCode 插件
OpenCode 插件向 agent 暴露工具。要创建一个能处理 402 响应的 x402-fetch 工具,请新建 .opencode/plugins/x402-payment.ts:
TypeScript
// Use base-sepolia for testing. Get test USDC from https://faucet.circle.com/
import type { Plugin } from "@opencode-ai/plugin";
import { tool } from "@opencode-ai/plugin";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
export const X402PaymentPlugin: Plugin = async () => ({
tool: {
"x402-fetch": tool({
description:
"Fetch a URL with x402 payment. Use when webfetch returns 402.",
args: {
url: tool.schema.string().describe("The URL to fetch"),
timeout: tool.schema.number().optional().describe("Timeout in seconds"),
},
async execute(args) {
const privateKey = process.env.X402_PRIVATE_KEY;
if (!privateKey) {
throw new Error("X402_PRIVATE_KEY environment variable is not set.");
}
// Your human-in-the-loop confirmation flow...
// const approved = await confirmPayment(args.url, estimatedCost);
// if (!approved) throw new Error("Payment declined by user");
const account = privateKeyToAccount(privateKey as `0x${string}`);
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const paidFetch = wrapFetchWithPayment(fetch, client);
const response = await paidFetch(args.url, {
method: "GET",
signal: args.timeout
? AbortSignal.timeout(args.timeout * 1000)
: undefined,
});
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.text();
},
}),
},
});
当内置的 webfetch 返回 402 时,agent 会调用 x402-fetch 携带支付信息重试。
Claude Code 钩子
Claude Code 钩子(hook)可以拦截工具的执行结果。要透明地处理 402 响应,请创建脚本 .claude/scripts/handle-x402.mjs:
JavaScript
// Use base-sepolia for testing. Get test USDC from https://faucet.circle.com/
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const input = JSON.parse(await readStdin());
const haystack = JSON.stringify(input.tool_response ?? input.error ?? "");
if (!haystack.includes("402")) process.exit(0);
const url = input.tool_input?.url;
if (!url) process.exit(0);
const privateKey = process.env.X402_PRIVATE_KEY;
if (!privateKey) {
console.error("X402_PRIVATE_KEY not set.");
process.exit(2);
}
try {
// Your human-in-the-loop confirmation flow...
// const approved = await confirmPayment(url);
// if (!approved) process.exit(0);
const account = privateKeyToAccount(privateKey);
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const paidFetch = wrapFetchWithPayment(fetch, client);
const res = await paidFetch(url, { method: "GET" });
const text = await res.text();
if (!res.ok) {
console.error(`Paid fetch failed: ${res.status}`);
process.exit(2);
}
console.log(
JSON.stringify({
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: `Paid for "${url}" via x402:\n${text}`,
},
}),
);
} catch (err) {
console.error(`x402 payment failed: ${err.message}`);
process.exit(2);
}
function readStdin() {
return new Promise((resolve) => {
let data = "";
process.stdin.on("data", (chunk) => (data += chunk));
process.stdin.on("end", () => resolve(data));
});
}
在 .claude/settings.json 中注册该钩子:
{
"hooks": {
"PostToolUse": [
{
"matcher": "WebFetch",
"hooks": [
{
"type": "command",
"command": "node .claude/scripts/handle-x402.mjs",
"timeout": 30
}
]
}
]
}
}
相关内容
- 从 Agents SDK 发起支付 — 使用 Agents SDK 获得更精细的控制
- 为 HTTP 内容收费 — 构建服务端
- Human-in-the-loop 指南 — 实现审批流程
- x402.org ↗ — 协议规范