# CCT - getCCIPAdmin() token with Burn and Mint Pool in forked environments
Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/cct-burn-and-mint-fork

> For the complete documentation index, see [llms.txt](/llms.txt).

> **NOTE: Integrate Chainlink Local into your project**
>
> ### npm
>
> If you use [NPM](https://www.npmjs.com/), install the [@chainlink/local NPM
> package](https://www.npmjs.com/package/@chainlink/local):
>
> ```shell
> npm install @chainlink/local@0.3.1
> ```
>
> ### yarn
>
> If you use [Yarn](https://yarnpkg.com/), install the [@chainlink/local NPM
> package](https://www.npmjs.com/package/@chainlink/local):
>
> ```shell
> yarn add @chainlink/local@0.3.1
> ```
>
> ### foundry
>
> If you use [Foundry](https://book.getfoundry.sh/):
>
> ```shell
> forge install smartcontractkit/chainlink-local@v0.3.1
> ```
>
> and then set remappings to: `@chainlink/local/=lib/chainlink-local/` in either `remappings.txt` or `foundry.toml` file
>
> ### RemixIDE
>
> Import the `CCIPLocalSimulator` contract from the `@chainlink/local` package:
>
> ```solidity
> import {CCIPLocalSimulator} from "@chainlink/local@0.3.1/src/ccip/CCIPLocalSimulator.sol";
> ```

This tutorial walks you through testing the procedure of enabling your own tokens in CCIP 2.0, using the [CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry)'s Example 06 (CCT 01). You will deploy a `CrossChainToken` (a CCT-compatible ERC-20 that exposes `getCCIPAdmin()`) with a Burn & Mint pool on forks of *Ethereum Sepolia* and *Polygon Amoy*, register it in the live TokenAdminRegistry, link the pools, opt them into Fast Transfers, and send a transfer across the CCIP 2.0 lane with `CCIPLocalSimulatorFork` routing the message.

The kit runs this exact flow as a fork test (`test/fork/Example06.fork.t.sol`), so every step below is verified against pinned forks of the live testnets.

## Prerequisites

> **NOTE: Prerequisites**
>
> Familiarize yourself with the [CCT standard](/ccip/concepts/cross-chain-token/overview) and [CCIP
> architecture](/ccip/concepts/architecture/overview) before proceeding with this tutorial. Complete the [Foundry forked environments guide](/chainlink-local/build/ccip/foundry/local-simulator-fork) first: it covers the fork profile, `evm_version = "cancun"`, Foundry >= 1.5.1 and the RPC setup.

Before we start with this guide, let's recap parts of the CCT standard that we will need for it.

### Requirements for Cross-Chain Tokens

Before enabling an ERC20-compatible token in CCIP, it's important to understand the requirements it must fulfill to integrate with CCIP.

- **Recommended permissionless token administrator address registration methods**: A token can use either of these supported function signatures to register permissionlessly:
  - `owner()`: This function returns the token contract owner's address.
  - `getCCIPAdmin()`: This function returns the token administrator's address and is recommended for new tokens, as it allows for abstraction of the CCIP Token Administrator role from other common roles, like `owner()`.

    > **NOTE**
    >
    > In the event that these functions are not available, registration can be performed by submitting a form
    > [here](https://chain.link/ccip-contact).

- **Requirements for CCIP token transfers**: The token's smart contract must meet minimum requirements to integrate with CCIP.
  - **Burn & Mint Requirements**:
    - The token smart contract must have the following functions:
      - `mint(address account, uint256 amount)`: This function is used to mint the `amount` of tokens to a given `account` on the destination blockchain.
      - `burn(uint256 amount)`: This function is used to burn the `amount` of tokens on the source blockchain.
      - `decimals()`: Returns the token's number of decimals.
      - `balanceOf(address account)`: Returns the current token balance of the specified `account`.
      - `burnFrom(address account, uint256 amount)`: This function burns a specified number of tokens from the provided account on the source blockchain. **Note**: This is an optional function. We generally recommend using the `burn` function, but if you use a tokenPool that calls `burnFrom`, your token contract will need to implement this function.
    - On the source and destination blockchains, the token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool.

If you don't have an existing token, the kit deploys a `CrossChainToken` for you: a `BaseERC20` with `getCCIPAdmin()`, mint and burn roles, and a pre-mint, configured in one constructor call.

### Understanding the Procedure

It is also important first to understand the overall procedure for enabling your tokens in CCIP. This procedure involves deploying tokens and token pools, registering administrative roles, and configuring token pools to enable secure token transfers using CCIP. The steps in the diagram below highlight the flow of actions needed to enable a token for cross-chain transfers. Whether you're working with an Externally Owned Account (EOA) or a **Smart Account** (such as one using a multisig scheme), the overall logic remains the same. You'll follow the same process to enable cross-chain token transfers, configure pools, and register administrative roles.

The diagram below outlines the entire process:

![Process for enabling a token in CCIP.](/images/ccip/CCIP_enabled_tokens_flowchart.jpg)

## Before You Begin

1. **Install Foundry >= 1.5.1**: If you haven't already, follow the instructions in the [Foundry documentation](https://book.getfoundry.sh/getting-started/installation).

2. **Clone the starter kit** and initialize the submodules one level deep:

   ```bash
   git clone https://github.com/smartcontractkit/ccip-starter-kit-foundry
   cd ccip-starter-kit-foundry
   git submodule update --init
   ```

3. **Set up your environment**: Copy `.env.example` to `.env` and fill in archive-capable RPC endpoints for both chains:

   ```bash
   ETHEREUM_SEPOLIA_RPC_URL=<ethereum_sepolia_rpc_url>
   POLYGON_AMOY_RPC_URL=<polygon_amoy_rpc_url>
   ```

4. **Import a keystore account** for script execution:

   ```bash
   cast wallet import myAccount --interactive
   ```

   This tutorial assumes `--account myAccount`.

> **NOTE: Optimizer is required for CCT pool deployment**
>
> `BurnMintTokenPool` can exceed the EVM max code size if compiled without the optimizer. The kit includes a dedicated
> CCT profile in `foundry.toml` (`[profile.cct]` with `optimizer = true, optimizer_runs = 200`). Use
> `FOUNDRY_PROFILE=cct` in all deployment commands of this tutorial.

## Step 1: Deploy the token and pool on Ethereum Sepolia (source)

Run the `DeployCCTBurnMintTokenAndPool` script. It deploys the `CrossChainToken` (name `TestToken`, symbol `TEST`, 18 decimals, `1_000_000e18` pre-mint to the broadcaster, `100_000_000e18` max supply), the BurnMint pool, grants the pool mint and burn roles on the token, registers the token admin via `getCCIPAdmin()`, accepts the admin role, and sets the pool in the TokenAdminRegistry:

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:DeployCCTBurnMintTokenAndPool \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,address,address,address)" \
  <SEPOLIA_TOKEN_ADMIN_REGISTRY> \
  <SEPOLIA_REGISTRY_MODULE_OWNER_CUSTOM> \
  <SEPOLIA_ARM_PROXY> \
  <SEPOLIA_ROUTER>
```

Read the four addresses from the [CCIP Directory](/ccip/directory/testnet) (or from `Register.getNetworkDetails(11155111)`). Save from the logs:

- `<SEPOLIA_TOKEN_ADDRESS>`
- `<SEPOLIA_POOL_ADDRESS>`

## Step 2: Deploy the token and pool on Polygon Amoy (destination)

Repeat the same script on Amoy:

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:DeployCCTBurnMintTokenAndPool \
  --rpc-url polygonAmoy \
  --account myAccount \
  --broadcast \
  --sig "run(address,address,address,address)" \
  <AMOY_TOKEN_ADMIN_REGISTRY> \
  <AMOY_REGISTRY_MODULE_OWNER_CUSTOM> \
  <AMOY_ARM_PROXY> \
  <AMOY_ROUTER>
```

Save `<AMOY_TOKEN_ADDRESS>` and `<AMOY_POOL_ADDRESS>`.

## Step 3: Configure the Sepolia pool with the Amoy remote

The `Example06` script links a pool to its remote chain: it sets the remote token, the remote pool, and (in this example) a disabled rate limiter. Re-running it for the same remote chain selector replaces the existing config:

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:Example06 \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address)" \
  <SEPOLIA_POOL_ADDRESS> \
  <AMOY_CHAIN_SELECTOR> \
  <AMOY_TOKEN_ADDRESS> \
  <AMOY_POOL_ADDRESS>
```

## Step 4: Configure the Amoy pool with the Sepolia remote

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:Example06 \
  --rpc-url polygonAmoy \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address)" \
  <AMOY_POOL_ADDRESS> \
  <SEPOLIA_CHAIN_SELECTOR> \
  <SEPOLIA_TOKEN_ADDRESS> \
  <SEPOLIA_POOL_ADDRESS>
```

## Step 5: (Optional) Enable Fast Transfers on both pools

New pools allow full finality only (`0x00000000`): every transfer waits for full finality. To opt a pool into Faster Than Finality, set its allowed finality config to a minimum block depth:

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:Example06SetPoolAllowedFinalityConfig \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,bytes4)" \
  <SEPOLIA_POOL_ADDRESS> \
  <ALLOWED_FINALITY_CONFIG>
```

Repeat on Amoy with `--rpc-url polygonAmoy` and `<AMOY_POOL_ADDRESS>`. `0x00000000` disables Faster Than Finality; values `0x00000001`-`0x0000ffff` set the minimum block depth. Read each pool back with the `Example06GetPoolAllowedFinalityConfig` script.

Before an FTF send, check the lane's finality gates (pool, executor and CCV settings) with the read-only `Example06CheckFinalityGates` script. A `getFee` quote alone does not prove `ccipSend` will succeed for a non-`IPoolV2` pool.

## Step 6: Send the transfer across the lane

Send with default finality (`blockConfirmations = 0`):

```bash
FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:SendCCTTokenWithExtraArgsV3DefaultFinality \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address,uint256,uint32,address)" \
  <SEPOLIA_ROUTER> \
  <AMOY_CHAIN_SELECTOR> \
  <RECEIVER_ON_AMOY> \
  <SEPOLIA_TOKEN_ADDRESS> \
  <AMOUNT> \
  <GAS_LIMIT> \
  <FEE_TOKEN_ADDRESS>
```

Parameter notes:

- `<AMOUNT>` uses token decimals (`1e18` is 1 token for an 18-decimal token).
- `<GAS_LIMIT>`: set to `0` for a token-only transfer to an EOA receiver.
- `<FEE_TOKEN_ADDRESS>`: the LINK token address on Sepolia to pay in LINK, or `0x0000000000000000000000000000000000000000` to pay in native.

For the Faster Than Finality version (after Step 5), use `SendCCTTokenWithExtraArgsV3CustomFinality` with the same signature plus `<BLOCK_CONFIRMATIONS_GT_ZERO>`.

The script logs the message ID. Monitor the message on [CCIP Explorer](https://ccip.chain.link).

## Run the same flow as a fork test

The kit's `test/fork/Example06.fork.t.sol` runs this entire procedure against pinned forks of the live testnets, with `CCIPLocalSimulatorFork` routing each message to the Amoy OffRamp. The core of the test:

```solidity
// Deploy on both forks through the real script
(sepoliaToken, sepoliaPool) = _deploy(sepoliaFork, sepolia);
(amoyToken, amoyPool) = _deploy(amoyFork, amoy);

// Link the pools through the real script
vm.selectFork(sepoliaFork);
configureLaneScript.run(sepoliaPool, amoy.chainSelector, amoyToken, amoyPool);
vm.selectFork(amoyFork);
configureLaneScript.run(amoyPool, sepolia.chainSelector, sepoliaToken, sepoliaPool);

// Send on Sepolia and route to the Amoy OffRamp
vm.selectFork(sepoliaFork);
sendDefaultFinality.run(
    sepolia.routerAddress, amoy.chainSelector, receiver, sepoliaToken, TRANSFER_AMOUNT,
    EOA_RECEIVER_GAS_LIMIT, address(0)
);
simulator.switchChainAndRouteMessage(amoyFork);

// Burn & Mint: supply shrinks on the source and grows on the destination
assertEq(_balanceOnFork(amoyFork, amoyToken, receiver), TRANSFER_AMOUNT);
assertEq(_supplyOnFork(amoyFork, amoyToken), destinationSupplyBefore + TRANSFER_AMOUNT);
```

Run it with:

```bash
FOUNDRY_PROFILE=fork forge test --match-contract Example06ForkTest -j 1
```

The fork test also covers the Faster Than Finality gates: it asserts the pool rejects block-depth requests until `setAllowedFinalityConfig` opts it in, then finds the smallest depth the live lane accepts and delivers the transfer at that depth.

## Verify the result on the live testnets

After the transfer finalizes, verify the registration and the lane mapping:

```bash
cast call <SEPOLIA_TOKEN_ADMIN_REGISTRY> "getPool(address)(address)" <SEPOLIA_TOKEN_ADDRESS> --rpc-url ethereumSepolia
cast call <SEPOLIA_POOL_ADDRESS> "getRemoteToken(uint64)(bytes)" <AMOY_CHAIN_SELECTOR> --rpc-url ethereumSepolia
cast call <SEPOLIA_POOL_ADDRESS> "getRemotePools(uint64)(bytes[])" <AMOY_CHAIN_SELECTOR> --rpc-url ethereumSepolia
```

Verify the Burn & Mint behavior (burn on source, mint on destination):

```bash
cast call <SEPOLIA_TOKEN_ADDRESS> "balanceOf(address)(uint256)" <SOURCE_EOA_ADDRESS> --rpc-url ethereumSepolia
cast call <AMOY_TOKEN_ADDRESS> "balanceOf(address)(uint256)" <RECEIVER_ON_AMOY> --rpc-url polygonAmoy
cast call <SEPOLIA_TOKEN_ADDRESS> "totalSupply()(uint256)" --rpc-url ethereumSepolia
cast call <AMOY_TOKEN_ADDRESS> "totalSupply()(uint256)" --rpc-url polygonAmoy
```

## Next steps

- For the Lock & Release flow (with a lock box and liquidity funding), read the [CCT lock and release tutorial](/chainlink-local/build/ccip/foundry/cct-lock-and-release-fork).
- For the full CCT registration procedure against live testnets (not forks), read [Register a new Burn and Mint token from an EOA](/ccip/evm/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry).