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

# Denylist layers

> The two independent address-screening layers enforced on every non-USDC transfer.

CCTP for non-USDC enforces two independent address-screening layers on every
non-USDC crosschain transfer. Both must clear before a transfer succeeds. They
differ in scope, in who manages them, and in where they execute.

## Layer 1: service-level denylist

Every transfer on this blockchain—outbound and inbound, regardless of
`TokenManagerType`—is screened against an `IDenylistProvider` configured on the
local `CrossChainTokenService`. The provider exposes a single view:

```solidity Solidity theme={null}
interface IDenylistProvider {
    function isDenylisted(address account) external view returns (bool);
}
```

* **Outbound:** every payable transfer entrypoint screens both `msg.sender` and
  `tx.origin`. Denied addresses cause the call to revert before the burn or lock
  happens.
* **Inbound:** the service screens the transfer recipient before delivering
  tokens. Denied recipients cause the inbound `receiveMessage` to revert with
  `DenylistedAddress(recipient)`.

The provider is set by the service operator using
[`setDenylistProvider`](/cctp/expanded-assets/references/contract-reference).
The check is fail-closed: if the provider's `staticcall` reverts, the transfer
reverts.

This layer applies to every token on this blockchain. There is no opt-out at the
token level.

## Layer 2: per-token denylist on the `CrossChainToken`

Each `CrossChainToken` (CCT) deployed as `NATIVE_CROSSCHAIN_TOKEN` can carry its
own `IDenylistProvider`. The CCT owner sets it by calling
[`updateDenylistProvider`](/cctp/expanded-assets/references/contract-reference).
Setting `address(0)` removes per-token screening; the service-level layer still
applies.

The check runs inside the ERC-20 `_update` and screens both `from` and `to` on
every transfer, mint, and burn. `transferFrom` additionally screens the spender
at the entrypoint. Denied accounts cause `AccountDenylisted(account)` to revert.

Layer 2 exists only for protocol-deployed tokens. `BURN_MINT` and `LOCK_UNLOCK`
configurations of pre-existing ERC-20 tokens do not have this layer; whatever
screening the underlying token implements applies independently.

## Initial provider on a new `CrossChainToken`

When the service deploys a `CrossChainToken`, the new contract's initial
`denylistProvider` value depends on the deploy path:

| Deploy path                                           | Initial `denylistProvider`               |
| ----------------------------------------------------- | ---------------------------------------- |
| Remote ownerless token (`deployRemoteOwnerlessToken`) | Service's `denylistProvider` (inherited) |
| Remote custom token (`deployRemoteCrossChainToken`)   | `address(0)` (owner opts in)             |
| Local custom token (`deployCrossChainToken`)          | `address(0)` (owner opts in)             |

In other words: ownerless crosschain tokens get Circle's screening list out of
the box on every remote domain. Custom tokens always start with no per-token
screening; the issuer opts in by calling `updateDenylistProvider` themselves.

## Comparison

| Aspect             | Layer 1 (service-level)                                                | Layer 2 (per-token)                                                     |
| ------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Where it lives     | Service storage; queried by `CrossChainTokenService`                   | The `CrossChainToken` (only `NATIVE_CROSSCHAIN_TOKEN`)                  |
| Configured by      | Service operator                                                       | The CCT owner                                                           |
| Applies to         | Every flow on this blockchain (outbound and inbound)                   | ERC-20 movements of the specific CCT, when its provider is non-zero     |
| Initial value      | Set during service initialization                                      | Inherited from the service for remote ownerless; `address(0)` otherwise |
| Provider interface | `isDenylisted(address)`                                                | `isDenylisted(address)`                                                 |
| Revert             | `DenylistedAddress(account)` (service) / fail-closed on provider error | `AccountDenylisted(account)` (CCT)                                      |

## Composition

The two layers are independent. Layer 1 fires at the service boundary; layer 2
fires inside the `CrossChainToken`'s `_update`. A transfer must clear both.

Layer 2 has a single provider slot. Setting a new provider replaces the previous
one—it does not chain. An owner who wants both Circle's screening and a custom
list must implement a composite provider that calls both upstream lists. The
service-level layer at the boundary is unaffected by what the owner does on the
CCT; layer 1 always runs.
