PAYMENT GUARD
Agents are starting to move real funds over x402 endpoints and crypto rails. The obvious way that goes wrong is paying a sanctioned or scam address by mistake, and on-chain that is irreversible. Payment Guard is the one call an agent makes before a transfer.
FreeDeterministic — no LLMOFAC + scam lists + on-chain + ENSHTTP + MCP
verdict appears here…
Try a sanctioned/scam address and it returns block.
| Endpoint | What it does |
|---|---|
/api/screen-address | The guard. Address or ENS name → OFAC-sanctioned? scam/abuse-listed? on-chain risk (brand-new/unused, contract)? → a verdict |
/api/screen-payment | Vet an x402/payment endpoint or merchant URL (punycode host, a known brand name outside the registrable domain, new domain, the final URL after redirects) |
/api/check-sanctioned | Fast OFAC sanctions check for an address / ENS name. The response states its own coverage |
/api/resolve-name | Resolve an ENS name → address, then screen that address against the sanctions and scam lists. Catches names that do not resolve |
/api/screen-token | Honeypot / sell-tax / blocklist check on a token contract. The buy+sell simulation is api.honeypot.is, Ethereum and Base only |
OFAC publishes sanctioned addresses by currency, not by chain. I ingest every upstream list that is
in EVM (0x) format — ETH, ARB, BSC, ETC, USDC, USDT — and match against the union, so one
result applies to every EVM chain.
Not checked: Bitcoin, Tron, Solana, Monero and the other non-EVM sanctioned
addresses. This API only accepts EVM addresses, so it could never match them. A sanctioned:
false means the address is absent from the EVM lists above and nothing more. Every response
carries a coverage object that says this in machine-readable form.
If the list fails to load, you get verdict: "unknown" and a
sanctions-check-unavailable flag. Never safe. An unanswered question is not
a clean result.
An earlier version read only the ETH list. That missed 4 addresses OFAC publishes under ARB, BSC,
USDC and USDT, and called them clear. The test suite now pins those four.
lib/ens.js does one thing: namehash the name, call resolver(node) on the
mainnet ENS registry, then call addr(bytes32) on that resolver. Whatever address comes
back is screened against the sanctions and scam lists. That is the whole check.
It does not detect lookalike or homoglyph names. There is no confusable check and no ENSIP-15 normalization. A name with a Cyrillic character in it is just another name to me — I resolve it and screen whatever address it points to.
Mainnet registry and a direct addr() call only. No ENSIP-10 wildcard resolution, no
CCIP-Read, so offchain and L2 names — Basenames, .cb.id, gasless subnames — come back as
not resolving here even though a wallet resolves them. Read "did not resolve" as "not via the mainnet
registry with a direct addr() call", not as "does not exist".
The buy+sell simulation is api.honeypot.is. It answers for Ethereum and Base, and
returns 400 Invalid chain for Polygon, Arbitrum and Optimism, so the honeypot
and taxes fields are not populated on those three. When no simulation ran the verdict is
never safe: you get caution (or block, if the scam blocklist
already hit), a honeypot-check-unavailable flag, and a honeypot_coverage
object naming the chains it supports.
The URL lookalike rule matches 14 brand names as a substring of the host and flags them when the
brand is not the registrable domain, so paypal.com.secure-pay.xyz is caught. There is no
edit distance and no homoglyph mapping, so paypa1.com is not caught by that rule. On
redirects I compare the final URL only, not each hop.
{ "mcpServers": { "payment-guard": { "command": "npx", "args": ["-y", "payment-guard-mcp"] } } }