Skip to main content
Version: 0.3.0

Sanctions Screening

The Hub integrates the Chainalysis sanctions oracle (canonical mainnet address 0x40C57923924B5c5c5455c48D93317139ADDaC8fb). Every public-layer balance movement, plus controller registration on the Hub, consults the oracle synchronously and reverts if either counterparty is flagged. Production deployments point at the Chainalysis oracle; chains without a Chainalysis deployment run with screening disabled.

Oracle binding

The oracle address lives on the Hub, not on individual EBEMTs — same pattern as trcPk, the freezer/seizer roles, and the freeze registry. One oracle address governs every EBEMT in the deployment.

// On EBHub
address public sanctionsList; // address(0) disables screening
function setSanctionsList(address newList) external onlyOwner;
function isAddressSanctioned(address addr) external view returns (bool);

isAddressSanctioned short-circuits to false when sanctionsList == address(0) or addr == address(0). The zero-address short-circuit is what lets the screening hook live in _update without breaking mint (from = 0) and burn (to = 0) paths.

setSanctionsList(address(0)) disables screening — useful as a circuit-breaker if the oracle misbehaves or for chains where no oracle is available. Owner-only; emits SanctionsListUpdated(oldList, newList).

Interface

interface ISanctionsList {
function isSanctioned(address addr) external view returns (bool);
}

Enforcement points

LayerSiteScreened
PublicEBEMT._update(from, to, value)from, to
Hub registryEBHub.registerEpk(epk, controller, …)controller
Hub registryEBHub.changeControllerWithAuth(epk, newController, …)newController

Public layer (_update)

The ERC-20 _update override is the single chokepoint for every public-layer credit and debit. The freeze check already lives there; the sanctions check is added next to it with the same skip-on-zero-address semantics. This covers:

  • transfer, transferFrom
  • publicMint, publicBurn
  • publicToEncryptedTransfer (the public-side burn)
  • encryptedToPublicTransfer (the public-side mint)
  • the autoEncrypt redirect (to is screened before redirection; from is screened on the burn)

Controller screening

registerEpk and changeControllerWithAuth are the only paths by which an EVM address becomes the spend-authorization point for an EPK. Screening the controller at these two entry points prevents sanctioned users from entering the encrypted layer through fresh registration or rotation. The check fires before signature consumption on the rotation path, so a rotation to a sanctioned controller never burns a controller nonce.

Encrypted-only operations between two already-registered EPKs do not re-screen — the controller was screened at registration time. If a controller becomes sanctioned mid-flight, the freeze tool (per Freeze and Seize) is the correct response: freeze the EPK to immobilize encrypted-layer movement, freeze the address to immobilize public-layer movement.

Seize bypasses screening

Both seizePublic and seizeEncrypted route their destination credit through super._update, bypassing the EBEMT _update override entirely. Sanctions checks are skipped on both ends of a seize, identical to the freeze bypass. This is intentional: seize is a court-ordered movement to a designated authority and must succeed even if either end is sanctioned.

Threat model and operational notes

Oracle availability is a liveness dependency. Once enabled, every public-layer movement performs a view call to the oracle. A reverting or out-of-gas oracle bricks the public layer. Mitigations:

  • The oracle is view-only — no state writes, no reentrancy surface.
  • The setter accepts address(0), providing an instant kill switch under owner control.
  • The Chainalysis oracle is read-cheap and operationally hardened; the mock matches its shape.

Bypass via clean controller. A sanctioned user can in principle register an EPK with a clean controller and use the encrypted layer through that controller. This is true of any address-based screening and is symmetric to KYC bypass at the EMI boundary; sanctions enforcement here is one defensive layer, not a complete control.

Soft consistency with freeze. Freeze and sanctions screening are independent checks at the same chokepoint and stack additively. A sanctioned-but-not-frozen address can be unblocked only by the oracle dropping the flag (issuer cannot override). A frozen-but-not-sanctioned address can be unblocked by unfreezeAddress. Both must clear for movement to succeed.

Events

EventSourcePurpose
SanctionsListUpdated(address oldList, address newList)HubOracle binding changed

The screening itself is silent — a sanctioned counterparty reverts the entire enclosing call (AddressIsSanctioned(addr)). Investigators rely on the oracle's own indexing (the Chainalysis list is published and queryable off-chain) rather than per-revert telemetry.