Privy has a good recipe for blocking x402 payments to sanctioned addresses: https://docs.privy.io/recipes/agent-integrations/x402-sanctions-screening
The enforcement point is Privy's policy engine. You load a denylist into a condition set, and Privy refuses to sign a payment whose recipient is on it. The recipe is clear about what it leaves to you: loading the addresses is "general code, so it is not reproduced here", the example source is the OFAC SDN list, and it suggests pairing the denylist with a screening provider.
This post shows one way to add that second layer, with a few lines of code and no extra account or API key.
What it adds
Before the agent pays an x402 endpoint, it asks a screening service to read that endpoint's own 402 and check every payTo it advertises against four lists: OFAC SDN, UN Consolidated, EU and UK. The answer is signed (EIP-191), with each list reported separately and its own version date, so you can keep it as a record of what was checked.
The check is itself paid over x402: 0.01 USDC on Base or Solana. The same wallet that pays the target pays for the check; in the setup below, that is your Privy wallet.
Keep the Privy policy as the control that is enforced at signing. Treat this as an extra layer: more lists than OFAC alone, and a signed record per payment for your audit trail.
The code (Node 20 or newer)
npm install @privy-io/node@0.35.0 @x402/fetch@2.17.0 # @privy-io/node/x402 also imports these at load time, so install them too: npm install @solana/kit@^5.1.0 @x402/svm@2.17.0 import { createX402Client } from '@privy-io/node/x402'; import { wrapFetchWithPayment } from '@x402/fetch'; const SCREEN = 'https://sanctions.nsgoods.org/screen-url?url='; // Pay the target only if every payTo it advertises is clear on all four lists. // Anything else (deny, review, an error, a timeout) means do not pay. export async function screenThenFetch(fetchWithPayment, targetUrl, init) { let res, body; try { res = await fetchWithPayment(SCREEN + encodeURIComponent(targetUrl)); body = await res.json(); } catch { throw new Error('screening unavailable: do not pay'); } if (res.status !== 200 || body.verdict !== 'clean') { throw new Error(`payee not clear (${res.status} ${body?.verdict ?? 'no verdict'}): do not pay`); } return fetchWithPayment(targetUrl, init); } // Privy wallet, as in Privy's x402 docs const x402client = createX402Client(privy, { walletId: wallet.id, address: wallet.address }); const fetchWithPayment = wrapFetchWithPayment(fetch, x402client); const response = await screenThenFetch(fetchWithPayment, 'https://api.example.com/premium'); The verdict is clean only when every payee is clear on all four lists. deny means an OFAC match. review means a UN, EU or UK match, or a list that could not be checked. A bad, unreachable or non x402 URL returns 400, 422 or 502 and you are not charged.
Limits
- The check reads the 402 at the moment you call it. The response includes
quote_sha256andquote_read_at, so you can log exactly what was checked, but the Privy policy is still what enforces at signing. - Passing means the payee is not on these four lists at their stated version dates. It says nothing about indirect exposure or where the funds go next. Not legal advice.
- We tested
screenThenFetchon Base with@x402/fetch2.17.0 and a plain EVM wallet: a clean payee was screened and then paid, and a non x402 URL was refused with no payment. We checked that@privy-io/node0.35.0 exportscreateX402Client, but we have not run a payment with a Privy account.
Free signed sample of the response: https://sanctions.nsgoods.org/screen-url/preview
Node and Python examples, with offline signature verification for /screen-multi: https://github.com/Nikoble1926/sanctions-check-before-pay
OpenAPI: https://sanctions.nsgoods.org/openapi.json
