> ## 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.

# CCTP for non-USDC architecture

> How the CCTP for non-USDC contracts work together to move non-USDC assets across blockchains.

Built on top of CCTP, this layer reuses
[`MessageTransmitterV2`](/cctp/references/contract-interfaces#messagetransmitterv2)
and [Iris](/cctp/references/attestation-verification) (Circle's attestation API)
to move arbitrary ERC-20 tokens across blockchains. Four additional contracts
handle token registration, balance changes, and the developer-facing API.

## Components

* **`CrossChainTokenService`**: The singleton entry point on each domain.
  Registers tokens, deploys remote token contracts, and routes crosschain
  transfers.
* **`TokenManager`**: Deployed per token on each domain. Moves tokens (mint,
  burn, lock, or unlock) and enforces per-token controls including rate limits,
  max transfer amount, and pause.
* **`CrossChainToken`**: An ERC-20 with `ERC20Permit` deployed for tokens that
  are natively crosschain. Holders call `crossChainTransfer` directly on the
  token.
* **`CrossChainTokenExecutable`**: Optional abstract helper for destination
  receivers. Integrators can implement `executeWithCrossChainToken` directly and
  return `EXECUTE_SUCCESS`. See [Hooks](/cctp/expanded-assets/concepts/hooks).

## Transfer flow

The following sequence shows a transfer from a source domain to a destination
domain:

```mermaid theme={null}
sequenceDiagram
    participant Sender
    participant CCTS as CrossChainTokenService<br/>(source)
    participant TM_S as TokenManager<br/>(source)
    participant MT_S as MessageTransmitterV2<br/>(source)
    participant Iris as Iris<br/>(offchain)
    participant MT_D as MessageTransmitterV2<br/>(destination)
    participant CCTS_D as CrossChainTokenService<br/>(destination)
    participant TM_D as TokenManager<br/>(destination)
    participant Recipient

    Sender->>TM_S: approve(amount)
    Sender->>CCTS: crossChainTransfer(tokenId, amount, ...)
    CCTS->>TM_S: takeToken(sender, amount)
    TM_S->>TM_S: burn or lock
    CCTS->>MT_S: sendMessage(...)
    MT_S-->>Iris: emit MessageSent
    Iris-->>MT_D: deliver attestation
    MT_D->>CCTS_D: receiveMessage(...)
    CCTS_D->>TM_D: giveToken(recipient, amount)
    TM_D->>Recipient: mint or unlock
```

The exact behavior at the source and destination `TokenManager` depends on the
[token's manager type](#token-manager-types).

## Component map

```mermaid theme={null}
graph TD
    CCTS["CrossChainTokenService<br/>(singleton)"]
    TM1["TokenManager<br/>tokenId 0x1234..."]
    TM2["TokenManager<br/>tokenId 0x5678..."]
    TM3["TokenManager<br/>tokenId 0xabcd..."]
    Token1[ERC-20 token A]
    Token2[ERC-20 token B]
    CCT["CrossChainToken<br/>(deployed by CCTP for non-USDC)"]

    CCTS --> TM1
    CCTS --> TM2
    CCTS --> TM3
    TM1 -.manages.-> Token1
    TM2 -.manages.-> Token2
    TM3 -.manages.-> CCT
```

The `CrossChainTokenService` is one contract per domain. It fronts every
`TokenManager` on that domain. A `TokenManager` is created for each registered
token and is identified across all domains by its `tokenId`.

## Token manager types

Every `TokenManager` has a type that determines how it moves tokens on the
source and destination blockchains during a transfer. The type is set when the
token is registered.

```solidity Solidity theme={null}
enum TokenManagerType {
    NATIVE_CROSSCHAIN_TOKEN,  // 0
    BURN_MINT,                // 1
    LOCK_UNLOCK               // 2
}
```

| Type                      | Source domain behavior                                 | Destination domain behavior                    | When to use                                                                  |
| ------------------------- | ------------------------------------------------------ | ---------------------------------------------- | ---------------------------------------------------------------------------- |
| `NATIVE_CROSSCHAIN_TOKEN` | Token self-burns from the sender                       | Token self-mints to the recipient              | The token is a `CrossChainToken` deployed by the protocol                    |
| `BURN_MINT`               | `TokenManager` pulls tokens from sender and burns them | `TokenManager` mints tokens to the recipient   | An existing issuer-controlled token where the issuer can grant minter rights |
| `LOCK_UNLOCK`             | `TokenManager` pulls tokens from sender and locks them | `TokenManager` unlocks tokens to the recipient | An existing token the protocol cannot mint or burn                           |

Most tokens use the same type on all blockchains. Exception: the ownerless model
pairs `LOCK_UNLOCK` on the home blockchain with `NATIVE_CROSSCHAIN_TOKEN` on
remote blockchains. See
[Ownerless versus custom tokens](/cctp/expanded-assets/concepts/ownerless-vs-custom-tokens).

## How tokens are identified

Each token is identified by its `tokenId`, a `bytes32` value derived from the
address that registered the token and a caller-chosen salt. The same `tokenId`
identifies the same token across every blockchain where it is registered.

You do not construct a `tokenId` manually. The `CrossChainTokenService` returns
it when you register or deploy a token, and exposes read helpers to rederive it
later:

* `getOwnerlessTokenId(tokenAddress)` for ownerless tokens
* `crossChainTokenId(deployer, salt)` for native `CrossChainToken` deployments
  (`deployCrossChainToken`)
* `crossChainTokenId(deployer, customTokenDeploySalt(deployer, salt))` for
  custom tokens. Calling `crossChainTokenId(deployer, salt)` alone yields a
  different id. `customTokenDeploySalt` is also the storage salt used to derive
  the custom token's `TokenManager`.

Because the derivation uses `(msg.sender, salt)`, using the same registrar
address and the same `salt` on every blockchain produces the same `tokenId`
everywhere. This is what lets a single value identify a token on every supported
blockchain without an offchain registry.

Once you have a `tokenId`, look up the local contracts:

* `resolveTokenManager(tokenId)` returns the `TokenManager` address.
* `resolveTokenAddress(tokenId)` returns the underlying ERC-20 address.

Both revert with `TokenNotRegistered(tokenId)` if no token manager has been
deployed for the supplied `tokenId` on the local domain. The predicted address
is deterministic through CREATE3, so callers that need a non-throwing existence
check can wrap the call in `try`/`catch` or perform their own `extcodesize`
check.

### Decimal consistency

The protocol does not scale token amounts when delivering transfers. A token
must use the same number of decimals on every blockchain where it is deployed.

## Transfer finality and fast burn allowance

Two transfer speeds are available, set by the `minFinalityThreshold` parameter
on `crossChainTransfer`:

* **Standard transfer** (`minFinalityThreshold = 2000`) waits for source
  blockchain finality before attestation.
* **Fast transfer** (`minFinalityThreshold = 1000`) attests on soft finality and
  consumes a per-token fast burn allowance.

Each token has a fast burn allowance maintained by Circle. The allowance is
denominated in USD and limits the total value that can be moved through fast
transfers of that token before the allowance refills. Standard transfers do not
consume the allowance. When a fast transfer is initiated, the protocol consumes
more than the face value of the transfer from the allowance to account for
short-term price movement of the token.

Before initiating a fast transfer, query `GET /v2/cctpx/allowances` to confirm
remaining capacity and that the token has FX pricing configured. If allowance is
insufficient or pricing data is unavailable (for example
`FX_SYMBOL_NOT_CONFIGURED`), Iris rejects the fast quote; there is no automatic
fallback. Request a standard quote (omit `PRE_FINALITY`) and use
`minFinalityThreshold = 2000` instead.

USDC and non-USDC fast transfers share the same `MessageTransmitterV2` and
finality thresholds, but each tracks its own allowance pool. USDC transfers
consume the USDC allowance; non-USDC transfers consume a per-token allowance.

## Forwarding service

An optional forwarding service delivers completed transfers to the recipient on
the destination blockchain. When forwarding is used, Circle relays the Iris
attestation and calls `receiveMessage` on the destination `MessageTransmitterV2`
on behalf of the sender, so neither the sender nor the recipient needs to submit
the attestation manually.

Forwarding is opt-in and must be requested when you obtain the fee quote from
the Iris API. The fee quote response includes a signed claim that you pass as
the `claim` parameter on `crossChainTransfer`. Transfers that omit the claim
proceed without automatic relay, and you or the recipient are responsible for
submitting the attestation to `MessageTransmitterV2`.

You might choose not to use forwarding if you manage delivery yourself, if you
are performing complex hook-based transactions that require manual relay
sequencing, or if you have your own relayer infrastructure.

## Reuse of CCTP infrastructure

No separate attestation service runs for non-USDC transfers. Every message flows
through the same `MessageTransmitterV2` and Iris (Circle's attestation API) that
CCTP uses for USDC transfers, including the same set of attesters, the same
finality thresholds, and the same message verification logic. USDC and non-USDC
transfers share the attestation pipeline but are otherwise independent. USDC
transfer behavior is unchanged.
