agents
Version:
A home for your AI agents
198 lines (196 loc) • 6.06 kB
JavaScript
import { base, baseSepolia } from "viem/chains";
import { processPriceToAtomicAmount } from "x402/shared";
import { exact } from "x402/schemes";
import { useFacilitator } from "x402/verify";
import { createWalletClient, http } from "viem";
import { createPaymentHeader } from "x402/client";
//#region src/mcp/x402.ts
function withX402(server, cfg) {
const { verify, settle } = useFacilitator(cfg.facilitator);
const x402Version = cfg.version ?? 1;
function paidTool(name, description, priceUSD, paramsSchema, annotations, cb) {
return server.registerTool(name, {
description,
inputSchema: paramsSchema,
annotations,
_meta: {
"agents-x402/paymentRequired": true,
"agents-x402/priceUSD": priceUSD
}
}, (async (args, extra) => {
const atomic = processPriceToAtomicAmount(priceUSD, cfg.network);
if ("error" in atomic) {
const payload = {
x402Version,
error: "PRICE_COMPUTE_FAILED"
};
return {
isError: true,
_meta: { "x402/error": payload },
content: [{
type: "text",
text: JSON.stringify(payload)
}]
};
}
const { maxAmountRequired, asset } = atomic;
const requirements = {
scheme: "exact",
network: cfg.network,
maxAmountRequired,
payTo: cfg.recipient,
asset: asset.address,
maxTimeoutSeconds: 300,
resource: `x402://${name}`,
mimeType: "application/json",
description,
extra: "eip712" in asset ? asset.eip712 : void 0
};
const headers = extra?.requestInfo?.headers ?? {};
const token = extra?._meta?.["x402/payment"] ?? headers["X-PAYMENT"];
const paymentRequired = (reason = "PAYMENT_REQUIRED", extraFields = {}) => {
const payload = {
x402Version,
error: reason,
accepts: [requirements],
...extraFields
};
return {
isError: true,
_meta: { "x402/error": payload },
content: [{
type: "text",
text: JSON.stringify(payload)
}]
};
};
if (!token || typeof token !== "string") return paymentRequired();
let decoded;
try {
decoded = exact.evm.decodePayment(token);
decoded.x402Version = x402Version;
} catch {
return paymentRequired("INVALID_PAYMENT");
}
const vr = await verify(decoded, requirements);
if (!vr.isValid) return paymentRequired(vr.invalidReason ?? "INVALID_PAYMENT", { payer: vr.payer });
let result;
let failed = false;
try {
result = await cb(args, extra);
if (result && typeof result === "object" && "isError" in result && result.isError) failed = true;
} catch (e) {
failed = true;
result = {
isError: true,
content: [{
type: "text",
text: `Tool execution failed: ${String(e)}`
}]
};
}
if (!failed) try {
const s = await settle(decoded, requirements);
if (s.success) {
result._meta ??= {};
result._meta["x402/payment-response"] = {
success: true,
transaction: s.transaction,
network: s.network,
payer: s.payer
};
} else return paymentRequired(s.errorReason ?? "SETTLEMENT_FAILED");
} catch {
return paymentRequired("SETTLEMENT_FAILED");
}
return result;
}));
}
Object.defineProperty(server, "paidTool", {
value: paidTool,
writable: false,
enumerable: false,
configurable: true
});
return server;
}
const toChain = (network) => {
switch (network) {
case "base": return base;
case "base-sepolia": return baseSepolia;
default: throw new Error(`Unsupported network: ${network}`);
}
};
function withX402Client(client, x402Config) {
const { network, account, version } = x402Config;
const wallet = createWalletClient({
account,
transport: http(),
chain: toChain(network)
});
const maxPaymentValue = x402Config.maxPaymentValue ?? BigInt(.1 * 10 ** 6);
const _listTools = client.listTools.bind(client);
const listTools = async (params, options) => {
const toolsRes = await _listTools(params, options);
toolsRes.tools = toolsRes.tools.map((tool) => {
let description = tool.description;
if (tool._meta?.["agents-x402/paymentRequired"]) {
const cost = tool._meta?.["agents-x402/priceUSD"] ? `$${tool._meta?.["agents-x402/priceUSD"]}` : "an unknown amount";
description += ` (This is a paid tool, you will be charged ${cost} for its execution)`;
}
return {
...tool,
description
};
});
return toolsRes;
};
const _callTool = client.callTool.bind(client);
const callToolWithPayment = async (x402ConfirmationCallback, params, resultSchema, options) => {
const res = await _callTool(params, resultSchema, options);
console.log("res", res);
const maybeX402Error = res._meta?.["x402/error"];
if (res.isError && maybeX402Error && maybeX402Error.accepts && Array.isArray(maybeX402Error.accepts) && maybeX402Error.accepts.length > 0) {
const accepts = maybeX402Error.accepts;
const confirmationCallback = x402ConfirmationCallback ?? x402Config.confirmationCallback;
if (confirmationCallback && !await confirmationCallback(accepts)) return {
isError: true,
content: [{
type: "text",
text: "User declined payment"
}]
};
const req = accepts.find((a) => a?.scheme === "exact" && a?.network === network) ?? accepts[0];
if (!req || req.scheme !== "exact") return res;
const maxAmountRequired = BigInt(req.maxAmountRequired);
if (maxAmountRequired > maxPaymentValue) return {
isError: true,
content: [{
type: "text",
text: `Payment exceeds client cap: ${maxAmountRequired} > ${maxPaymentValue}`
}]
};
const token = await createPaymentHeader(wallet, version ?? 1, req);
return _callTool({
...params,
_meta: {
...params._meta,
"x402/payment": token
}
}, resultSchema, options);
}
return res;
};
const _client = client;
_client.listTools = listTools;
Object.defineProperty(_client, "callTool", {
value: callToolWithPayment,
writable: false,
enumerable: false,
configurable: true
});
return _client;
}
//#endregion
export { withX402, withX402Client };
//# sourceMappingURL=x402.js.map