跳到正文
DEV Community· Dutiful·· 2 小時前AI 評分47

如何透過 x402 讓 Agent 付費讀取公司背景報告

How an agent pays for and reads a company check over x402

AI 導讀

Dutiful 示範以 x402 讓 Agent 在 HTTP 請求中支付費用並取得公司背景報告,無須帳戶或 API key。服務會先免費檢查覆蓋範圍及確認公司實體,再按每份報告 $3.00 USDC 收費,報告包括登記資料、制裁篩查及新聞和網絡搜尋。服務覆蓋歐洲、以色列及北美的 12 個國家,並提供包含三項工具的 MCP server。

正文

Disclosure: I build Dutiful, the service used in this example.

Agents are good at calling APIs and bad at filling in sign-up forms. x402 is a way to pay for an HTTP request in the request itself: the server answers 402 Payment Required with the terms, the client signs a payment, and retries. No account, no API key, no billing page. This post walks through that flow with a real endpoint, a company background check.

What the service returns

Give it a company name and a country. It checks coverage for free, then, once paid ($3.00 USDC per report), returns a report: registry profile, officers and owners where the register publishes them, a sanctions screen (OFAC SDN and UK Sanctions List) and a news and web scan. Each finding names its source, and the report lists what it could not check. It covers 12 countries across Europe, Israel and North America, partially in the US and Canada.

The flow

  1. Create an order (free coverage check): POST https://dutiful.work/api/order {"company":"Tesco PLC","country":"GB"} The response lists candidate entities, so the agent can pick the right one.
  2. Select the entity: POST /api/order/{order}/entity {"idx":0} This answers 402 with the x402 payment requirements: amount, payee and network (USDC on Base).
  3. Pay: sign the requirements with the agent's wallet and send the signature in the PAYMENT-SIGNATURE header: POST /api/order/{order}/pay
  4. Read the result: GET /api/order/{order} -> status, report, report_text

Coverage is checked and the entity chosen before any payment is requested.

Signing the payment

Step 3 is where the client does the work: it reads the 402 body, builds the authorization for your wallet, and retries with the header. A minimal working client (Node 18+, viem), tested against the x402 facilitator on testnet:

// Minimal x402 client for Dutiful: check a company, pay in USDC on Base, read the report.
// Needs Node 18+ and `npm i viem`. Set PRIVATE_KEY to a wallet holding USDC on Base.
// Usage: PRIVATE_KEY=0x... node dutiful-x402-client.mjs "Tesco PLC" GB
import { privateKeyToAccount } from "viem/accounts";
import { randomBytes } from "node:crypto";

const BASE = process.env.DUTIFUL_URL || "https://dutiful.work";
const J = { "content-type": "application/json" };

export async function buyReport(company, country, { privateKey, baseUrl = BASE, fetchImpl = fetch, pickIdx = 0 } = {}) {
  const account = privateKeyToAccount(privateKey);
  const call = async (path, body, headers = {}) => {
    const r = await fetchImpl(baseUrl + path, { method: "POST", headers: { ...J, ...headers }, body: JSON.stringify(body || {}) });
    return { status: r.status, json: await r.json().catch(() => ({})) };
  };

  // 1. Free coverage check: candidate entities, no payment.
  const order = await call("/api/order", { company, country, ref: "devto-report" });
  if (order.status !== 200 || !order.json.candidates?.length) throw new Error("no coverage: " + JSON.stringify(order.json));
  const id = order.json.order;

  // 2. Pick the entity: the server answers 402 with the x402 payment requirements.
  const quote = await call(`/api/order/${id}/entity`, { idx: pickIdx });
  if (quote.status !== 402) throw new Error("expected 402, got " + quote.status);
  const req = quote.json.accepts[0]; // { scheme:"exact", network, asset, amount, payTo, extra:{name,version} }

  // 3. Sign an EIP-3009 transferWithAuthorization for exactly that amount (gasless for the payer).
  const chainId = Number(req.network.split(":")[1]);
  const auth = {
    from: account.address, to: req.payTo, value: BigInt(req.amount),
    validAfter: 0n, validBefore: BigInt(Math.floor(Date.now() / 1000) + req.maxTimeoutSeconds),
    nonce: "0x" + randomBytes(32).toString("hex"),
  };
  const signature = await account.signTypedData({
    domain: { name: req.extra.name, version: req.extra.version, chainId, verifyingContract: req.asset },
    types: { TransferWithAuthorization: [
      { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" },
      { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" } ] },
    primaryType: "TransferWithAuthorization", message: auth,
  });
  const payload = { x402Version: 2, accepted: req, payload: { signature, authorization: Object.fromEntries(Object.entries(auth).map(([k, v]) => [k, typeof v === "bigint" ? v.toString() : v])) } };
  const header = Buffer.from(JSON.stringify(payload)).toString("base64");

  // 4. Submit payment, then poll for the report.
  const paid = await call(`/api/order/${id}/pay`, {}, { "PAYMENT-SIGNATURE": header });
  if (paid.status !== 200) throw new Error("payment rejected: " + JSON.stringify(paid.json));
  for (let i = 0; i < 60; i++) {
    const s = await (await fetchImpl(`${baseUrl}/api/order/${id}`)).json();
    if (s.status === "delivered") return s;
    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error("not delivered yet; keep order id " + id + " and poll GET /api/order/" + id);
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const [company, country] = process.argv.slice(2);
  const r = await buyReport(company, country, { privateKey: process.env.PRIVATE_KEY });
  console.log(r.report_text || JSON.stringify(r, null, 2));
}

Needs a wallet holding USDC on Base. The authorization is gasless for the payer.

As a tool in an agent

There is also an MCP server with three tools: check_coverage (free), create_order (returns the payment requirements) and get_report. Docs, openapi.json and .well-known/x402.json are linked from https://dutiful.work/developers .

What to know

Sales are final. Your report stays on its private link for at least a week after delivery - download the PDF to keep a copy. The report is research from public sources, not a compliance certificate. Sample report: https://dutiful.work/sample . Questions or problems: dutifulhq@proton.me.

I would like feedback on the two-step flow and on what else an agent needs before it trusts a payment endpoint.

來源:DEV Community · dev.to