> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cryptocheckout.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sanctions screening

> Identity-blind on-chain list lookups, two layers, both fail-open. Not KYC.

Implemented 2026-05-21; stance locked 2026-05-08. Plan: `docs/superpowers/plans/2026-05-21-sanctions-screening.md`.

## The stance

Screening uses the **public Chainalysis on-chain oracle** — `isSanctioned(address)` — not the REST Sanctions API.

The reasoning is structural rather than technical: the oracle is permissionless. No API key, no signup, no terms that can be changed to paid or revoked. It is queried through the existing `eth_call` infrastructure, so it introduces no new vendor dependency in the payment path.

OFAC lists are chain-agnostic, so every address is screened against the canonical Ethereum-mainnet oracle at `0x40C5…8fb`, with a Base override at `0x3A91…739B`. Selector `0xdf592f7d`.

The trade-off: the oracle is boolean-only, with no OFAC name or description. That is fine — a block-or-allow decision needs only the boolean.

## Two layers

### Merchant wallets — default on, platform-level

Screened in `siwe-verify` at sign-in for merchant-role wallets, plus a daily re-scan cron at 03:17 UTC. A sanctioned wallet gets `403 wallet_blocked` with no JWT and no cookie. The verdict is cached on `merchants.sanctions_status` (`clean` | `sanctioned` | `pending`) with `sanctions_checked_at`.

<Info>
  On-chain `setRecipient` cannot be blocked off-chain — the registry is ownerless and permissionless by design. The platform's control point is therefore the **sign-in gate**, not the chain.
</Info>

### Customer wallets — default off, per-merchant opt-in

The merchant enables `sanctions_screening` in Settings → Compliance. When enabled, `create-payment-intent` screens the payer wallet plumbed from the widget's connected account. A sanctioned payer receives a deliberately vague `payment_blocked` error code, to avoid coaching evasion.

<Warning>
  Payer verdicts are **never persisted**. The screening is identity-blind and must stay that way.
</Warning>

## Both layers fail open

`screenAddress` returns `{kind: 'unknown'}` on RPC outage, and the payment proceeds.

This is deliberate. A screening outage should not halt legitimate commerce, and the merchant sign-in gate remains the platform-level enforcement point regardless.

## TRON deviation

<Warning>
  There is **no Chainalysis on-chain oracle on TRON** — it is EVM-only — so `T…` addresses cannot use the oracle path at all.
</Warning>

`_shared/sanctions-tron.ts` implements the forced alternative. Two layers, both keyless, identity-blind, and fail-open:

1. **Tether USDT `isBlackListed`** — an on-chain read via TronGrid `triggerconstantcontract`. This is Tether's freeze set, **not OFAC** — roughly a superset, and it can lag. It doubles as a UX pre-check, since a blacklisted payer's `transferFrom` would revert anyway.
2. **TRM Labs' free keyless Sanctions API** — for OFAC parity. Rate limited to 1 request per second and 100 per day.

This is a documented deviation from the EVM oracle-only stance, forced by the absence of an oracle. It gates **TRON mainnet only**; testnet and demo proceed without it.

## Pre-commit screening

Before a recipient, treasury, or partner address is baked into a pool's immutable init code, all of them are screened, per VM.

This is self-protection: a sanctioned or frozen address inside an immutable pool permanently locks funds for every party in that pool. The partner is screened here regardless of sign-in history, because a referrer auto-created from a code may never have signed in.

## The framing

Repeat consistently across docs, landing pages, and sales:

> **Sanctions-clean. KYC-free. We screen the wallet, not the person.**

## A common misconception, rebutted

<Danger>
  A DEX swap inside `RouterFee.payWithSwap` does **not** clean the on-chain attribution. The merchant's transaction history still shows the sanctioned-payer trigger, and Coinbase, Binance, and Kraken inbound-deposit screening will flag it on cash-out.
</Danger>

This rebuts the "the swap cleans the tokens" intuition, which comes up regularly.

## Regulatory framing

Sanctions screening is a **list lookup**, not a regulated crypto-asset service under MiCA Article 3(1). Refusing service to a sanctioned wallet is the *negation* of a regulated service, not the provision of one. Full analysis in [MiCA position](/compliance/mica) and `docs/MICA_REGULATORY_POSITION.md` §5b.

## Code

`_shared/sanctions.ts` and its tests, `_shared/sanctions-tron.ts`, `create-payment-intent`, `siwe-verify/handler.ts`, `cron-rescan-sanctions`. Migrations `20260521010000` for the columns and `20260521020000` for the cron.
