Screen a crypto address against OFAC and EU sanctions lists

An agent paying on-chain — an x402 payTo, a payout wallet, a refund address — can clear it against OFAC and EU sanctioned addresses in one paid call.

By Matthias Begot · · Updated

An autonomous agent that pays over x402 is signing a transfer to an address it did not choose: the payTo came out of a 402 challenge, a discovery listing, or a config file someone else maintains. One paid call — GET /screen/address — checks that address against the sanctioned addresses published by OFAC (SDN and Consolidated) and by the EU financial sanctions list, and returns the designated entity behind any hit, its programmes and its designation date. Bitcoin, EVM, Tron, Solana and Monero. The lookup runs against a local snapshot, so the address is never forwarded to a third party.

The problem: the payee address arrives from a machine surface

In a human payment flow, someone eyeballs the destination. In an agentic flow there is no one to eyeball it. The address reaches the signer through the protocol itself:

  • An x402 payTo. The x402 v2 specification defines an accepts array in the 402 response, each entry carrying a scheme, a CAIP-2 network, an asset contract and a payTo recipient address. The agent picks an entry and signs against that address. If it discovered the resource from a machine index rather than from a human integration — see what happens between a 402 challenge and the paid response — nothing in that path involved a compliance review.
  • A payout or refund wallet supplied by a counterparty, a marketplace seller or a support ticket.
  • A treasury instruction an agent is executing on a schedule against a wallet book that was cleared once, at onboarding.

The exposure is asymmetric. OFAC has published digital currency addresses inside SDN entries since 2018, and its Sanctions Compliance Guidance for the Virtual Currency Industry (October 2021) is explicit on two points that matter here: firms should screen virtual currency addresses against the listed ones, and OFAC may impose civil penalties on a strict liability standard — liability does not require knowledge that the counterparty was designated. An agent that cannot say which lists it screened, and as of when, has no answer to give afterwards.

The lists are not static. Recent designations show the cadence: OFAC updated the Central Bank of Iran designation on 24 April 2026, adding two Tron addresses to the SDN list after a $344 million USDT freeze; on 20 May 2026 it designated a Sinaloa Cartel network, adding six Ethereum addresses. OFAC publishes list changes continuously, on no fixed schedule. An address cleared at onboarding is not an address cleared today.

Why OFAC-only is not enough

The free address feeds circulating in the ecosystem are OFAC-only — they are built by extracting the digital-currency fields from the SDN XML. The EU publishes sanctioned addresses too, and they are not a subset.

The clearest example is Garantex. The EU listed the exchange in its 16th sanctions package, adopted 24 February 2025, together with associated wallet addresses — the first time the EU directly designated a Russian crypto exchange. Against an OFAC-only checker, those EU addresses return silence. /screen/address ingests the EU consolidated financial sanctions list alongside the OFAC lists, and every response names which list produced the hit.

Coverage is the first difference. Attribution is the second: a hit is not a boolean and not a score. It carries entity_name, list_source, programs, listed_on and the address exactly as the list published it — enough for the agent to log a reason, not just a refusal.

The call

GET /screen/address?address=3My1ffQr5qQzmq4aBFgRqwRYqfB5zPAt8t
{
  "query": {
    "address": "3My1ffQr5qQzmq4aBFgRqwRYqfB5zPAt8t",
    "address_normalized": "3My1ffQr5qQzmq4aBFgRqwRYqfB5zPAt8t",
    "chain_family": "bitcoin",
    "chain_family_source": "inferred"
  },
  "verdict": "sanctioned",
  "hits": [
    {
      "entity_id": "EU:EU.12747.39",
      "entity_name": "Garantex",
      "list_source": "EU-FSF",
      "programs": ["UKR"],
      "listed_on": "2025-02-24",
      "chain": "BTC",
      "address_published": "3My1ffQr5qQzmq4aBFgRqwRYqfB5zPAt8t"
    }
  ]
}

The verdict is binary — sanctioned or not_listed — and the evidence sits in hits. Note the list_source: EU-FSF, not OFAC.

Normalization is per chain family, and the normalized form is returned

Address equality is not one rule. The endpoint applies the rule the chain family dictates and hands back the form it compared:

  • EVM — lowercased, 0x enforced. The EIP-55 checksum is not verified and never causes a rejection, so a mixed-case address copied from a block explorer and a lowercased one from a database both match.
  • base58 (Bitcoin legacy, Tron, Solana) — case preserved, because it is significant.
  • bech32 — lowercased.

Whitespace inside the value is collapsed, which matters more than it sounds: an address pasted out of a PDF invoice usually carries some.

chain is law when you give it

Send chain and it is law: an address without that family’s shape is refused with 400 UNRECOGNIZED_ADDRESS_FORMAT, unbilled, rather than quietly reinterpreted as another family. Omit it and the family is inferred from the shape, with the answer stating which happened in query.chain_family_source (provided vs inferred).

For an agent, that is the difference between a guess and a check. If your pipeline already knows it is paying on Base, declaring chain=evm turns a malformed value into a hard stop instead of a lookup against the wrong family.

What a not_listed verdict actually asserts

not_listed is the answer most calls return, so it has to be precise about its own scope. Every response — hit or no hit — carries lists_screened, an array of { list, addresses } counts, including lists that publish zero addresses:

"lists_screened": [
  { "list": "EU-FSF", "addresses": 7 },
  { "list": "OFAC-CONS", "addresses": 0 },
  { "list": "OFAC-SDN", "addresses": 961 },
  { "list": "UN", "addresses": 0 }
]

A clean verdict therefore cannot be mistaken for broader coverage than it has. Alongside it, provenance.freshness is a snapshot with an as_of date, and a fixed limits sentence restates the boundaries in the payload itself. The three together are what an agent should log next to the transaction: these lists, at these sizes, as of this date.

The x402 golden rule, applied to a verdict

The agent pays for the answer to its question. “This address is not on these lists, as of this date” is a successful answer → 200. That is the whole point of the endpoint: the clean result is the one you will get almost every time, and it is the one that carries the audit value.

The 4xx range is reserved for requests the service cannot answer, and those are never charged: a missing address, a chain outside the five families, or a value that is not a recognizable address at all. That last case is deliberate — a password, a truncated string or a misplaced IBAN dropped into the address parameter must not be billed as a verdict.

A concrete shape: the pre-signature gate

Wiring this in front of an x402 payment is four steps:

  1. Read the challenge. Parse the 402 response and pick an entry from accepts; extract its payTo.
  2. Screen it. One GET /screen/address, with chain declared from the entry’s CAIP-2 network when you know it.
  3. Branch. On sanctioned, do not sign — log entity_name, list_source, programs and listed_on as the reason. On not_listed, proceed.
  4. Log provenance.freshness.as_of and lists_screened next to the settlement hash.

The screening call is itself an x402 call, so it runs the same discover → 402 → pay → replay loop as the payment it is gating; the Quickstart walks that loop with runnable snippets and For agents covers the discovery surfaces. Prices and accepted rails are served live by the catalog — never hardcoded, here or anywhere else.

Two practical notes. A payTo is usually stable per seller, so the sane policy is to cache the verdict per address with its as_of date and re-screen on a schedule rather than on every request. And because the endpoint answers from a local snapshot in milliseconds, the gate does not meaningfully change the latency budget of the payment it protects.

Files, payout runs and re-screening

The same engine takes a list. POST /screen/address/batch screens up to 500 addresses in one call and one settlement, returning a summary of sanctioned / not_listed / error counts and one result per item, in submission order, each with its index. A malformed item is flagged at its position without failing the batch.

lists_screened, limits and provenance are stated once for the batch — every address cleared against the same snapshot, in the same call. That consistency is exactly what a row-by-row loop cannot give you, and it is what makes the batch the natural re-screening primitive: after a list update, re-clearing a whole wallet book is one scheduled call, not a project. The same argument, on the fiat rail, is developed in screening a vendor file of IBANs in bulk.

What this gate does not do

The scope is narrow on purpose, and stated in the endpoint documentation:

  • Only addresses the lists publish themselves. No derived addresses, no associated addresses, no addresses inferred from an entity’s other holdings.
  • No transaction analysis, no clustering, no risk scoring. An address that received funds from a designated address is not a hit here. That is a different product making a different claim, and conflating the two would make both verdicts worthless.
  • Absence from an official list is not a safety signal. not_listed means no signal on the covered lists as of the snapshot date. It is not a compliance opinion and does not discharge your own AML obligations.
  • It screens an address, not a party. To screen a company or person name against the same lists, use GET /screen/entity. For a bank transfer rather than an on-chain payment, the equivalent gate is GET /preflight/payment, which combines IBAN validity, bank and BIC resolution, jurisdiction risk and payee-name screening into one verdict.

Used for what it is — one settled call that clears an exact address against the OFAC and EU published lists, with the designating entity attached and the snapshot dated — it turns the one control an autonomous payer cannot perform by eye into something it can run before every transfer. Field tables, the full error contract and the chain-is-law rule are in the GET /screen/address documentation.