arrow-left

All pages
gitbookPowered by GitBook
1 of 1

Loading...

Exercise 4: Sending USDC Cross-Chain

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/arrow-up-right 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)arrow-up-right

hashtag
The @chainlink/contracts-ccip NPM package

To use Chainlink CCIP, you need to interact with Chainlink CCIP-specific contracts from the NPM package.

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.

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.

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:

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

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.

hashtag
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

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

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

hashtag
Prepare for deployment

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

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 and search for "avalanche fuji". Once you see the network with Chain ID 43113, click the "Add to Metamask" button.

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 and search for "sepolia". Once you see the network with Chain ID 11155111, click the "Add to Metamask" button.

hashtag
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,

  • 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 as the linkToken and

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

Metamask notification will pop up. Sign the transaction.

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

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

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

Go to the 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.

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.

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.

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

0x5425890298aed601595a70AB815c96711a31Bc65 as the usdcToken.
  • 0, as the _gasLimit parameter

  • CCIP Starter Kit (Foundry version)arrow-up-right
    @chainlink/contracts-cciparrow-up-right
    https://faucet.circle.com/arrow-up-right
    https://faucets.chain.link/fujiarrow-up-right
    Faucets | Chainlinkarrow-up-right
    Chainlist.orgarrow-up-right
    Chainlist.orgarrow-up-right
    Avalanche Fuji Snowtrace Explorerarrow-up-right
    Chainlink CCIP Explorerarrow-up-right
    CCIP Explorer | Chainlinkarrow-up-right
    arrow-up-right
    Circle Faucet
    Chainlink Faucet
    Connect your wallet to Remix IDE
    Add Avalanche Fuji network to Metamask
    Approve 1 USDC to be spent by TransferUSDC.sol
    Approve 1 USDC to be spent by TransferUSDC.sol
    // 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 {}
    // 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);
        }
    }
    Logo
    Address Details - Snowtrace Testnet Multichain Blockchain ExplorerSnowtrace Blockchain Explorerchevron-right
    Approve 1 USDC to be spent by TransferUSDC.sol
    Logo
    Testnet Faucet | Circlefaucet.circle.comchevron-right
    Circle Faucet
    Logo
    Remix: Returned error: {"jsonrpc":"2.0","error":"invalid opcode: PUSH0", "id":2405507186007008}Stack Overflowchevron-right
    Setting solc EVM version in different environments
    https://www.npmjs.com/package/@chainlink/contracts-ccipwww.npmjs.comchevron-right
    Logo