# Using the Chainlink Local Simulator in your Foundry project with forked environments
Source: https://docs.chain.link/chainlink-local/build/ccip/foundry/local-simulator-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";
> ```

You can use Chainlink Local to run CCIP 2.0 in forked environments within your Foundry project. To get started quickly, you will use the
[CCIP Foundry Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-foundry). The kit's `test/fork` directory contains fork tests for the CCT examples (`Example06`-`Example10`), which run the real example scripts against pinned forks of *Ethereum Sepolia* and *Polygon Amoy* (a live CCIP 2.0 lane) and route each message to the destination OffRamp with `CCIPLocalSimulatorFork`.

`CCIPLocalSimulatorFork` routes messages on forked networks for every CCIP era: pre-1.6 (`EVM2EVMOnRamp`), 1.6 and CCIP 2.0 (CCV-based lanes, which live testnet lanes run today). On CCIP 2.0 lanes the destination OffRamp selects the CCVs and executes the message (`V2VerificationMode.OFFRAMP_DERIVED`, the default); CCV attestations are simulated with a synthetic no-op verifier.

## Prerequisites

This guide assumes that you are familiar with the guide [Using the Chainlink Local Simulator in your Foundry project](/chainlink-local/build/ccip/foundry/local-simulator).
If not, please get familiar with it and run all the prerequisites.

Fork tests have two requirements beyond the no-fork setup:

1. **Foundry >= 1.5.1.**
2. **`evm_version = "cancun"` or later for the fork tests.** Deployed CCIP 2.0 contracts use Cancun opcodes; compiling with `paris` fails with `EvmError: NotActivated`. Use a fork-only profile so your deployed bytecode is not affected. The kit's `foundry.toml` does exactly this:

   ```toml
   [profile.fork]
   optimizer = true
   optimizer_runs = 200
   no_match_path = 'test/none/**'
   match_path = 'test/fork/**'
   ```

   `evm_version = 'cancun'` is set at the top level of the kit's `foundry.toml`.

Set up an `.env` file (or export the variables) with the fork RPC URLs. The kit needs archive-capable endpoints for both chains:

- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL for the Ethereum Sepolia testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/),
  [Infura](https://www.infura.io/), or another node provider service.
- `POLYGON_AMOY_RPC_URL`: The RPC URL for the Polygon Amoy testnet, from the same providers.

## Test a CCT transfer across a CCIP 2.0 lane

You will run the fork test for Example 06 (CCT 01: `CrossChainToken` + BurnMint pool). The test file `Example06.fork.t.sol` is located in the `./test/fork` directory. It deploys a custom token and pool on both forks, registers them in the live TokenAdminRegistry, links the pools, and sends token transfers across the Sepolia → Amoy CCIP 2.0 lane. The file contains seven test cases, including:

1. **Deploy**: Creates the token and pool on both chains and registers them.
2. **Configure lane**: Sets the remote token and pool on each pool.
3. **Send with default finality**: Burns on the source and mints on the destination.
4. **Send with Faster Than Finality**: Reverts until both pools opt in, then delivers after the pools opt in.

In your terminal, run the following command to execute the test:

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

Example output:

```text
$ FOUNDRY_PROFILE=fork forge test --match-contract Example06ForkTest -j 1
Ran 7 tests for test/fork/Example06.fork.t.sol:Example06ForkTest
[PASS] test_checkFinalityGates_findsRegisteredPool() (gas: 3066525)
[PASS] test_configureLane_rerunReplacesExistingConfig() (gas: 942262)
[PASS] test_configureLane_setsRemoteTokenAndPool() (gas: 747587)
[PASS] test_deploy_createsTokenAndPoolAndRegistersThem() (gas: 240027)
[PASS] test_send_defaultFinalityBurnsOnSourceAndMintsOnDestination() (gas: 2510920)
[PASS] test_send_fasterThanFinalityDeliversAfterPoolsOptIn() (gas: 3481610)
[PASS] test_send_fasterThanFinalityRevertsUntilPoolOptsIn() (gas: 1444822)
Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 2.42s (15.51ms CPU time)

Ran 1 test suite in 2.42s (2.42s CPU time): 7 tests passed, 0 failed, 0 skipped (7 total tests)
```

## Examine the code

### Setup

The shared base contract `test/fork/utils/CCTForkTest.sol` sets up both forks and the simulator. To route messages across forks, the test needs:

- A fork of the source chain (Ethereum Sepolia) and the destination chain (Polygon Amoy)
- The network details (router, registry, RMN proxy addresses) for both chains
- The fork simulator, made persistent so it can route across forks
- A sender account (the broadcaster) and a receiver account

The `setUp()` function is invoked before each test case:

1. Create the forks. Both are pinned to a committed block (`DEFAULT_SEPOLIA_FORK_BLOCK`, `DEFAULT_AMOY_FORK_BLOCK`) so a fresh clone runs the same chain state. Override the pins per run with the `ETHEREUM_SEPOLIA_FORK_BLOCK` / `POLYGON_AMOY_FORK_BLOCK` environment variables:

   ```solidity
   sepoliaFork =
       vm.createSelectFork("ethereumSepolia", vm.envOr("ETHEREUM_SEPOLIA_FORK_BLOCK", DEFAULT_SEPOLIA_FORK_BLOCK));
   amoyFork = vm.createFork("polygonAmoy", vm.envOr("POLYGON_AMOY_FORK_BLOCK", DEFAULT_AMOY_FORK_BLOCK));
   ```

2. Initialize the [fork Chainlink Local simulator](/chainlink-local/api-reference/v0.3.1/ccip-local-simulator-fork#cciplocalsimulatorfork) and make it persistent. The simulator must be created on a fork and be persistent so it can route across forks:

   ```solidity
   simulator = new CCIPLocalSimulatorFork();
   vm.makePersistent(address(simulator));
   ```

3. Read the network details for both chains with [`getNetworkDetails`](/chainlink-local/api-reference/v0.3.1/ccip-local-simulator-fork#getnetworkdetails). [`Register.NetworkDetails`](/chainlink-local/api-reference/v0.3.1/register#networkdetails) stores the chain selector, router address, LINK address, wrapped native address, CCIP test tokens, RMN proxy, registry module and token admin registry addresses:

   ```solidity
   sepolia = simulator.getNetworkDetails(block.chainid);
   vm.selectFork(amoyFork);
   amoy = simulator.getNetworkDetails(block.chainid);
   ```

4. Fund the broadcaster on both forks and verify the lane is the CCIP 2.0 lane the test expects. The test fails with a clear message if the pinned block or the lane is not what it describes:

   ```solidity
   vm.selectFork(sepoliaFork);
   vm.deal(broadcaster, 100 ether);
   vm.selectFork(amoyFork);
   vm.deal(broadcaster, 100 ether);
   vm.selectFork(sepoliaFork);

   address onRamp = IRouterOnRampView(sepolia.routerAddress).getOnRamp(amoy.chainSelector);
   assertTrue(onRamp != address(0), "Sepolia router has no OnRamp for Amoy at the pinned block");
   assertEq(ITypeAndVersionView(onRamp).typeAndVersion(), "OnRamp 2.0.0", "Sepolia -> Amoy is not a CCIP 2.0 lane");
   ```

5. Contracts or scripts created in a fork test are per-fork: make them persistent before using them on another fork:

   ```solidity
   sendDefaultFinality = SendCCTTokenWithExtraArgsV3DefaultFinality(
       _persistent(address(new SendCCTTokenWithExtraArgsV3DefaultFinality()))
   );
   ```

### Send and route the message

The `_sendDefaultFinalityAndRoute` helper shows the two-step flow used by every fork test:

1. On the source fork, run the send script. It builds an `ExtraArgsV3` message with `blockConfirmations = 0` (default finality) and calls `ccipSend` through the Sepolia router:

   ```solidity
   vm.selectFork(sepoliaFork);
   sendDefaultFinality.run(
       sepolia.routerAddress, amoy.chainSelector, receiver, token, amount, EOA_RECEIVER_GAS_LIMIT, address(0)
   );
   ```

2. Call [`switchChainAndRouteMessage`](/chainlink-local/api-reference/v0.3.1/ccip-local-simulator-fork#switchchainandroutemessage) to switch to the destination fork and route the message. The simulator reads the recorded logs, finds the `CCIPMessageSent` event, and executes the message on the Amoy OffRamp. The active fork is Amoy when the call returns:

   ```solidity
   simulator.switchChainAndRouteMessage(amoyFork);
   ```

Routing is strict by default: `switchChainAndRouteMessage` reverts with `CCIPLocalSimulatorFork__MessageNotRouted(messageId, reason)` when a captured message cannot be routed to any of the given forks, and with `CCIPLocalSimulatorFork__MessageExecutionFailed(messageId, reason)` (the decoded revert data) when it does not execute successfully. Call [`setStrictRouting(false)`](/chainlink-local/api-reference/v0.3.1/ccip-local-simulator-fork#setstrictrouting) to record failures instead and read them with [`getMessageStatus`](/chainlink-local/api-reference/v0.3.1/ccip-local-simulator-fork#getmessagestatus).

The Example 07 fork test (`test_send_failsToExecuteWhenDestinationLockBoxIsEmpty`) shows the strict-routing failure path: it sends a Lock & Release transfer without funding the destination lock box, so the OffRamp cannot release the tokens, and expects the routing call to revert:

```solidity
vm.expectPartialRevert(CCIPLocalSimulatorFork.CCIPLocalSimulatorFork__MessageExecutionFailed.selector);
simulator.switchChainAndRouteMessage(amoyFork);
```

### Faster Than Finality across the lane

The `test_send_fasterThanFinalityDeliversAfterPoolsOptIn` test case shows the CCIP 2.0 finality gates on a real lane:

1. New pools allow full finality only, so any block-depth request is rejected by the pool. The test first asserts the pool's default and expects the send to revert:

   ```solidity
   assertEq(TokenPool(sepoliaPool).getAllowedFinalityConfig(), FinalityCodec.WAIT_FOR_FINALITY_FLAG);
   vm.expectPartialRevert(FinalityCodec.InvalidRequestedFinality.selector);
   ```

2. Opt both pools in with `setAllowedFinalityConfig` (through the kit's `Example06SetPoolAllowedFinalityConfig` script), then find the smallest depth the live lane accepts by quoting `getFee` for candidate depths:

   ```solidity
   vm.selectFork(sepoliaFork);
   _setPoolMinBlockDepth(sepoliaPool, 1);
   uint16 depth = _findLaneFtfDepth(sepoliaToken);
   _setPoolMinBlockDepth(sepoliaPool, depth);
   vm.selectFork(amoyFork);
   _setPoolMinBlockDepth(amoyPool, depth);
   ```

3. Send with the custom finality (`blockConfirmations = depth`) and route. The transfer burns on the source and mints on the destination, same as the default-finality case, but delivered before full finality.

## What the fork simulator does not model

Not modelled by the simulator (and not asserted in the kit's fork tests): real CCV attestations, RMN and executor fees. CCVs on the lane are replaced by a synthetic verifier, and `executePendingV2Message` executes without charging the executor fee a real manual execution costs. Pinned state never sees testnet upgrades, so green tests only prove the examples against that snapshot; re-pin the fork blocks when the lane's behavior changes or on every `@chainlink/contracts-ccip` or `@chainlink/local` bump.

## Next steps

- The other fork tests cover Lock & Release pools with a lock box (`Example07.fork.t.sol`), `AdvancedPoolHooks` (`Example08`-`Example10`). For pool-required CCVs on `AdvancedPoolHooks`, read the [pool-based CCVs tutorial](/chainlink-local/build/ccip/foundry/pool-based-ccvs-fork).
- To fork mainnets, read [Using the Chainlink Local Simulator to fork mainnets](/chainlink-local/build/ccip/foundry/forking-mainnets).
- For the full CCT enabling procedure on the Sepolia → Amoy lane, read the [CCT burn and mint tutorial](/chainlink-local/build/ccip/foundry/cct-burn-and-mint-fork) and the [CCT lock and release tutorial](/chainlink-local/build/ccip/foundry/cct-lock-and-release-fork).
- For an index of every kit example and where its topic is documented, read the [CCIP starter kit examples map](/chainlink-local/learn/starter-kit-examples).