An x402 client pays whatever payTo the server puts in its 402 response. Budget caps answer "how much". Nothing in the default flow answers "to whom".
So we gave our pay guard the Lazarus Group's wallet as the payTo of a real, healthy seller and asked whether to pay:
curl "https://factline.dev/v1/pay/guard?url=https://api.onesource.io/&pay_to=0x098B716B8Aaf21512996dC57EB0615e2383E2f96&amount_usd=0.05&max_usd=1"
{
"decision": "deny",
"reasons": ["payTo 0x098B716B8Aaf21512996dC57EB0615e2383E2f96 is on the OFAC sanctions list (LAZARUS GROUP)"],
"seller": { "origin": "https://api.onesource.io", "known": true, "payers_30d": 321, "uptime_30d": 1, "median_ms": 1197 },
"ttl": 600
}
The seller itself is fine (321 paying wallets in 30 days, answered every nightly check). The wallet is not. That is the case a budget cap never catches: a prompt-injected or swapped payTo on an otherwise normal endpoint.
What it checks
- payTo against the OFAC list. OFAC is the list that publishes wallet addresses. (EU, UN and UK lists cover people and companies; those are a separate screen.)
- The seller's track record from our nightly probes of x402 Bazaar sellers: does it answer a valid 402, how many wallets paid it in 30 days, how long has it been listed. Unknown sellers get a domain age check.
-
Your budget:
amount_usdagainstmax_usd, plus the seller's usual price for comparison.
Answer: allow, review or deny, with reasons in plain English. Free, cached for 10 minutes per seller and payTo.
One line on an x402 client
npm i @factline/x402-pay-guard
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { withPayGuard } from "@factline/x402-pay-guard";
const client = withPayGuard(
new x402Client().register("eip155:8453", new ExactEvmScheme(signer)),
{ maxUsd: 1, onReview: "block" },
);
const fetchWithPay = wrapFetchWithPayment(fetch, client);
The official x402 client already has a hook that runs right before it signs (onBeforePaymentCreation, with selectedRequirements.payTo). The guard plugs into that, so a deny aborts before any signature exists. If the guard is unreachable it pays by default (failOpen: true); set it to false if you would rather not pay unchecked. MCP agents can call the same check as the x402_pay_guard tool on https://factline.dev/mcp/kyc.
What it does not do
It is a denylist and a track record, not chain analytics. It will not see indirect exposure (a fresh wallet funded by a listed one). If you need that, pair it with a risk-scoring provider. For an agent paying small x402 bills, a free check of the obvious cases before signing is the part most clients are missing today.
Code: github.com/factline-dev/x402-pay-guard. Live seller and facilitator health: factline.dev/x402/health and factline.dev/x402/facilitators.
Top comments (0)