> For the complete documentation index, see [llms.txt](https://cll-devrel.gitbook.io/ccip-masterclass-4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cll-devrel.gitbook.io/ccip-masterclass-4/ccip-masterclass/exercise-1-cross-chain-transfer-usdc.md).

# Exercise #1: Cross-Chain Transfer USDC

Coding time

## Getting started

You can use Chainlink CCIP with any blockchain development framework. For this Masterclass, we will use Remix IDE.

Let's create a new project by navigating to <https://remix.ethereum.org/> and clicking the "Create new Workspace" button. Select "Blank" template and name the workspace as "CCIP Masterclass 4".

Alternatively, you can clone:

* [CCIP Starter Kit (Hardhat version)](https://github.com/smartcontractkit/ccip-starter-kit-hardhat)
* [CCIP Starter Kit (Foundry version)](https://github.com/smartcontractkit/ccip-starter-kit-foundry)

## The @chainlink/contracts-ccip NPM package

To use Chainlink CCIP, you need to interact with Chainlink CCIP-specific contracts from the [@chainlink/contracts-ccip](https://www.npmjs.com/package/@chainlink/contracts-ccip) NPM package.

{% embed url="<https://www.npmjs.com/package/@chainlink/contracts-ccip>" %}

To install it, create a new Solidity file, and paste the following content. It is an empty contract that just imports one of the contracts from the `@chainlink/contracts-ccip` and `@openzeppelin/contracts` packages that we will use throughout this Masterclass as well.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Empty {}
```

This contract expects at least `0.8.20` version of a Solidity compiler. It is very important to understand that with the latest Remix IDE release, the default EVM version is set to "Cancun".  A new opcode, PUSH0, was added to the Ethereum Virtual Machine in the Shanghai upgrade, which happened prior to the current, Cancun upgrade.&#x20;

However, besides Ethereum, the majority of blockchains haven't included PUSH0 opcode.

That means the PUSH0 opcode can now be part of the contract's bytecode and if the chain you are working on does not support it, it will error with the "Invalid opcode" error.

**What we want is to downgrade Ethereum Virtual Machine version to "Paris" instead.**

To understand more, we highly encourage you to check this StackOverflow answer:

{% embed url="<https://stackoverflow.com/a/76332341>" %}
Setting solc EVM version in different environments
{% endembed %}

To set EVM version to "Paris", navigate to the "Solidity compiler" tab and then:

* Set "COMPILER" version to `0.8.20+commit.a1b79de6`
* Toggle the "Advanced Configurations" dropdown
* Toggle the "EVM VERSION" dropdown menu and select `paris` instead of `default`

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2F9xC5L5x8qzIMYk4K7LIh%2FScreenshot%202024-02-07%20at%2012.26.32%E2%80%AFPM.png?alt=media&amp;token=4f980641-60df-4500-863e-c857fc0e4674" alt=""><figcaption></figcaption></figure>

Now compile the smart contract by clicking the "Compile Empty.sol" button. If compiled successfully, go back to "File explorer" tab and if new `.deps/npm/@chainlink/contracts-ccip` and `.deps/npm/@openzeppelin/contracts` folders are generated, that means we imported all of the necessary packages into the Remix IDE Workspace successfully.

## Faucet

During this Masterclass, we will transfer USDC from Avalanche Fuji testnet to Ethereum Sepolia testnet. To get some amount of testnet USDC on Avalanche Fuji testnet, navigate to the <https://faucet.circle.com/>

{% embed url="<https://faucet.circle.com/>" %}
Circle Faucet
{% endembed %}

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2Fmlw1Ju7XoMbutQ7vBrRa%2FScreenshot%202024-01-26%20at%2011.29.41%E2%80%AFAM.png?alt=media&amp;token=c8f5e261-c459-4d1f-9493-c30bea5e661d" alt=""><figcaption><p>Circle Faucet</p></figcaption></figure>

To pay for CCIP Fees you can use either LINK token or native/wrapped native asset on a given blockchain. For this Masterclass we will need at least 3 LINK or Avalanche Fuji testnet. To get it, navigate to the <https://faucets.chain.link/fuji>

{% embed url="<https://faucets.chain.link/fuji>" %}
Chainlink Faucet
{% endembed %}

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2F7WIBFrL5oruqMSWjCFrh%2FScreenshot%202024-01-26%20at%2011.25.01%E2%80%AFAM.png?alt=media&amp;token=3773292d-2c9f-47a1-84dd-c523ecd8c136" alt=""><figcaption><p>Chainlink Faucet</p></figcaption></figure>

## Develop TransferUSDC smart contract

Create a new Solidity file by clicking on the "Create new file" button, name it `TransferUSDC.sol`, and paste the following Solidity code.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/utils/SafeERC20.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
contract TransferUSDC is OwnerIsCreator {
    using SafeERC20 for IERC20;

    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees);
    error DestinationChainNotAllowlisted(uint64 destinationChainSelector);
    error NothingToWithdraw();

    IRouterClient private immutable i_ccipRouter;
    IERC20 private immutable i_linkToken;
    IERC20 private immutable i_usdcToken;

    mapping(uint64 => bool) public allowlistedChains;

    modifier onlyAllowlistedChain(uint64 _destinationChainSelector) {
        if (!allowlistedChains[_destinationChainSelector])
            revert DestinationChainNotAllowlisted(_destinationChainSelector);
        _;
    }

    event UsdcTransferred(
        bytes32 messageId,
        uint64 destinationChainSelector,
        address receiver,
        uint256 amount,
        uint256 ccipFee
    );

    constructor(address ccipRouter, address linkToken, address usdcToken) {
        i_ccipRouter = IRouterClient(ccipRouter);
        i_linkToken = IERC20(linkToken);
        i_usdcToken = IERC20(usdcToken);
    }

    function allowlistDestinationChain(
        uint64 _destinationChainSelector,
        bool _allowed
    ) external onlyOwner {
        allowlistedChains[_destinationChainSelector] = _allowed;
    }

    function transferUsdc(
        uint64 _destinationChainSelector,
        address _receiver,
        uint256 _amount,
        uint64 _gasLimit
    )
        external
        onlyOwner
        onlyAllowlistedChain(_destinationChainSelector)
        returns (bytes32 messageId)
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: address(i_usdcToken),
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;

        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: _gasLimit})
            ),
            feeToken: address(i_linkToken)
        });

        uint256 ccipFee = i_ccipRouter.getFee(
            _destinationChainSelector,
            message
        );

        if (ccipFee > i_linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(
                i_linkToken.balanceOf(address(this)),
                ccipFee
            );

        i_linkToken.approve(address(i_ccipRouter), ccipFee);

        i_usdcToken.safeTransferFrom(msg.sender, address(this), _amount);
        i_usdcToken.approve(address(i_ccipRouter), _amount);

        // Send CCIP Message
        messageId = i_ccipRouter.ccipSend(_destinationChainSelector, message);

        emit UsdcTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _amount,
            ccipFee
        );
    }

    function withdrawToken(
        address _beneficiary,
        address _token
    ) public onlyOwner {
        uint256 amount = IERC20(_token).balanceOf(address(this));

        if (amount == 0) revert NothingToWithdraw();

        IERC20(_token).transfer(_beneficiary, amount);
    }
}
```

## Prepare for deployment

Navigate to the "Deploy & run transactions" tab and select the "Injected Provider - Metamask" option from the "Environment" dropdown menu.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2Ft94UcRlC0UVhSFck4Gxj%2FScreenshot%202024-01-26%20at%2012.39.45%E2%80%AFPM.png?alt=media&amp;token=770504e5-2a9f-44c5-8644-c8d7936227b8" alt=""><figcaption><p>Connect your wallet to Remix IDE</p></figcaption></figure>

If you are using Metamask wallet, make sure you have added Avalanche Fuji C-Chain and Ethereum Sepolia (should already be added by default) networks.

Go to [Chainlist.org](https://chainlist.org/?testnets=true\&search=avalanche+fuji) and search for "avalanche fuji". Once you see the network with Chain ID 43113, click the "Add to Metamask" button.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FsEOn0XAm1qLjwu9tnXxF%2FScreenshot%202024-01-26%20at%2012.37.32%E2%80%AFPM.png?alt=media&amp;token=90e134f4-bf4f-4542-ad3f-532a8ed5528d" alt=""><figcaption><p>Add Avalanche Fuji network to Metamask</p></figcaption></figure>

Ethereum Sepolia should already be added by default to your Metamask wallet. However, if you need to manually add it, you can always repeat the same step we did for Avalanche Fuji C-Chain. Navigate to [Chainlist.org](https://chainlist.org/?testnets=true\&search=sepolia) and search for "sepolia". Once you see the network with Chain ID 11155111, click the "Add to Metamask" button.

## Step 1) Deploy TransferUSDC.sol to Avalanche Fuji

Open your Metamask wallet and switch to the Avalanche Fuji network.

Open the `TransferUSDC.sol` file.

Navigate to the "Solidity Compiler" tab and click the "Compile TransferUSDC.sol" button.

Navigate to the "Deploy & run transactions" tab and select the "Injected Provider - Metamask" option from the "Environment" dropdown menu. Make sure that `chainId` is switched to 43113 (if not, you may need to refresh the Remix IDE page in your browser).

Under the "Contract" dropdown menu, make sure that the "TransferUSDC - TransferUSDC.sol" is selected.

Locate the orange "Deploy" button. Provide:

* `0xF694E193200268f9a4868e4Aa017A0118C9a8177` as the `ccipRouter`,&#x20;
* `0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846` as the `linkToken` and
* &#x20;`0x5425890298aed601595a70AB815c96711a31Bc65` as the `usdcToken`.

Click the orange "Deploy"/"Transact" button.

Metamask notification will pop up. Sign the transaction.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FHZsBLOQjnuyryqGMkWmz%2FScreenshot%202024-01-26%20at%203.16.36%E2%80%AFPM.png?alt=media&amp;token=cfbc8ae2-cd65-455d-89d6-699a81ca98cb" alt=""><figcaption></figcaption></figure>

## Step 2) On AvalancheFuji, call allowlistDestinationChain function

Under the "Deployed Contracts" section, you should find the `TransferUSDC.sol` contract you previously deployed to Avalanche Fuji. Find the `allowlistDestinationChain` function and provide:

* 16015286601757825753, which is the CCIP Chain Selector for the Ethereum Sepolia test network, as the `_destinationChainSelector` parameter,
* `true` as `_allowed` parameter

Hit the "Transact" orange button.

## Step 3) On AvalancheFuji, fund TransferUSDC.sol with 3 LINK

To cover for CCIP fees, fund `TransferUSDC.sol` with some amount of LINK, 3 should be enough for this demo.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FlPlOmtK3be42f4cW6a48%2FScreenshot%202023-12-12%20at%203.52.12%E2%80%AFPM.png?alt=media&amp;token=a473af8d-f770-4a1f-bd7a-a2187075bd39" alt="" width="353"><figcaption><p>Fund TransferUSDC with LINK</p></figcaption></figure>

## Step 4) On Avalanche Fuji, call approve function on USDC.sol

Go to the [Avalanche Fuji Snowtrace Explorer](https://testnet.snowtrace.io/address/0x5425890298aed601595a70AB815c96711a31Bc65/contract/43113/writeProxyContract?chainId=43113) and search for USDC token. Locate the "Contract" tab, then click the "Write as Proxy" tab. Connect your wallet to the blockchain explorer. And finally find the "approve" function.

{% embed url="<https://testnet.snowtrace.io/address/0x5425890298aed601595a70AB815c96711a31Bc65/contract/43113/writeProxyContract?chainId=43113>" %}
Approve 1 USDC to be spent by TransferUSDC.sol
{% endembed %}

We want to approve 1 USDC to be spent by the `TransferUSDC.sol` on our behalf. To do so we must provide:

* The address of the `TransferUSDC.sol` smart contract we previously deployed, as `spender` parameter
* 1000000, as `value` parameter.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FL8va3UbSpMF3VRzH0X08%2FScreenshot%202024-01-26%20at%203.19.57%E2%80%AFPM.png?alt=media&amp;token=d369ca8d-dbe7-4d6d-8c61-a714577c899e" alt=""><figcaption><p>Approve 1 USDC to be spent by TransferUSDC.sol</p></figcaption></figure>

Because USDC token has 6 decimals, 1000000 means that we will approve 1 USDC to be spent on our behalf.

Click the "Write" button. Metamask popup will show up. Sign the transaction.

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FDHCQf6GjNAMT00MbBng6%2FScreenshot%202024-01-10%20at%2012.24.47%E2%80%AFPM.png?alt=media&amp;token=6b0eb21a-3984-4b9c-8d68-d836eed6fa25" alt="" width="358"><figcaption><p>Approve 1 USDC to be spent by TransferUSDC.sol</p></figcaption></figure>

## Step 5) On AvalancheFuji, call transferUsdc function

Under the "Deployed Contracts" section, you should find the `TransferUSDC.sol` contract you previously deployed to Avalanche Fuji. Find the `transferUsdc` function and provide:

* 16015286601757825753, which is the CCIP Chain Selector for the Ethereum Sepolia test network, as the `_destinationChainSelector` parameter,
* Your wallet address, as the `_receiver` parameter,
* 1000000, as the `_amount` parameter
* 0, as the `_gasLimit` parameter

0 is set as the `_gasLimit` parameter because we are sending tokens to an EOA so there is no cost for executing the `ccipReceive` function on the destination side.

Hit the "Transact" orange button.

You can now monitor the live status of your cross-chain message by copying the transaction hash into the search bar of a [Chainlink CCIP Explorer](https://ccip.chain.link/).

{% embed url="<https://ccip.chain.link/>" %}

<figure><img src="https://3061948411-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fwq7E0NNsaqCDcPKWe7Us%2Fuploads%2FDS5UTIMuTU3Kz56Pxmbj%2FScreenshot%202024-01-09%20at%201.52.46%E2%80%AFPM.png?alt=media&amp;token=d2c5db5d-33c7-4cca-a565-b8d6a063ad17" alt=""><figcaption></figcaption></figure>
