# Exercise #1: Transfer Tokens

## Welcome to the first CCIP Masterclass Exercise 🎉

Now that we learned the basics of Chainlink CCIP let's use that knowledge to bridge tokens from one blockchain to another. In exercise number one of this Masterclass, you will use Chainlink CCIP to transfer tokens from a smart contract to an EOA on a different blockchain.

## A brief intro to token handling mechanisms

To transfer tokens using CCIP, token pools on both blockchains must exist. That's why, if you remember the first chapter of this Masterclass, we said that you can transfer only supported tokens, not all of them.&#x20;

We are going to cover Token Pools later in the [CCIP Architecture in Depth](https://andrej-rakic.gitbook.io/chainlink-ccip/ccip-masterclass/ccip-architecture-in-depth) chapter, but briefly, the CCIP token bridge can support multiple token handling mechanisms at source & destination blockchains. Token handling mechanisms are a key aspect of how token transfers work. They each have different characteristics with trade-offs for issuers, holders, and DeFi applications.&#x20;

1. **Burn & Mint**\
   Tokens are burned on the source chain and minted natively on the destination chain<br>
2. **Lock & Mint (Reverse: Burn & Unlock)**\
   Tokens are locked on the source chain (in Token Pools), and wrapped/synthetic/derivative tokens that represent the locked tokens are minted on the destination chain.<br>
3. **Lock & Unlock** *\[ON THE ROADMAP]*\
   Transferred tokens are locked on the source chain (in Token Pools) and unlocked from Token Pools on the destination chain. This feature is not live yet.

## Faucet

Public faucets sometimes limit how many tokens a user can create and token pools might not have enough liquidity. To resolve these issues, CCIP supports two test tokens that you can mint permissionlessly so you don't run out of tokens while testing different scenarios.

As already said, there are two ERC20 test tokens currently available on each testnet - **CCIP-BnM** and **CCIP-LnM** (and its wrapped/synthetic representation, **clCCIP-LnM**).

<table><thead><tr><th width="150">Name</th><th width="131.66666666666666">Decimals</th><th>Type</th></tr></thead><tbody><tr><td>CCIP-BnM</td><td>18</td><td>Burn &#x26; Mint</td></tr><tr><td>CCIP-LnM</td><td>18</td><td>Lock &#x26; Mint (Reverse: Burn &#x26; Unlock)</td></tr></tbody></table>

### CCIP-BnM

These tokens are minted natively on each testnet because the token contract is deployed on each testnet. When transferring these tokens between testnet blockchains, CCIP burns the tokens on the source chain and mints them on the destination chain.

### CCIP-LnM

These tokens are only minted on **Ethereum Sepolia** because the token contract is only deployed to Sepolia. On other testnet blockchains, the token is represented as a wrapped/synthetic asset called ***clCCIP-LnM*** because it is not natively deployed to other testnets. When transferring these tokens from Ethereum Sepolia to another testnet, CCIP locks the ***CCIP-LnM*** tokens on the source chain and mints the wrapped representation ***clCCIP-LnM*** on the destination chain. Between non-Ethereum Sepolia chains, CCIP burns and mints the wrapped representation ***clCCIP-LnM***.

You can mint both of these tokens using the `drip` function call on the token contract. This function acts like a faucet. Each call mints 10\*\*18 units of a token to the specified address.

* For *CCIP-BnM*, you can call `drip` on all testnet blockchains.
* For *CCIP-LnM*, you can call `drip` only on Ethereum Sepolia.

The `drip` function is implemented on these tokens as follows:

```solidity
function drip(address to) external {
  _mint(to, 1e18);
}
```

To call this function, you can use Block Explorer like Etherscan, Foundry's `cast send` command, and more... But the easiest way to get those tokens is probably through the [Official Chainlink Documentation](https://docs.chain.link/ccip/test-tokens#mint-tokens-in-the-documentation). Navigate to the linked documentation page and connect your wallet by clicking the "Connect Wallet" button.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FDtmNF2JKz8kSqvzSkQ4A%2Ffaucet%20-%20step%201.png?alt=media&#x26;token=3d437049-e82a-47e5-939d-bfa56a34e572" alt="" width="563"><figcaption><p>Faucet - step 1</p></figcaption></figure>

Once connected, switch to **Avalanche Fuji** Testnet and mint 1 **CCIP-BnM** token. You should also add the **CCIP-BnM** token to your MetaMask using the button.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FRYPkpGy19HvYR0jqXWPj%2Ffaucet%20-%20step%202.png?alt=media&#x26;token=297f12b2-b25a-4510-a647-7b554feba9ee" alt="" width="563"><figcaption><p>Faucet - step 2</p></figcaption></figure>

## Develop CCIPTokenSender.sol

In this exercise, you will transfer CCIP-BnM tokens from a smart contract on Avalanche Fuji to an Externally Owned Account on Ethereum Sepolia. Obviously, you can reuse this contract for different lanes and for transferring other supported tokens as well.

{% tabs %}
{% tab title="Hardhat" %}
You should already have [`@chianlink/contracts`](https://www.npmjs.com/package/@chainlink/contracts) and [`@chainlink/contracts-ccip`](https://www.npmjs.com/package/@chainlink/contracts-ccip) NPM packages installed. If not, please install them by referring to [The @chainlink/contracts-ccip NPM package](https://andrej-rakic.gitbook.io/chainlink-ccip/getting-started/how-to-use-chainlink-ccip#the-chainlink-contracts-ccip-npm-package) and [Develop CCIP Sender contract](https://andrej-rakic.gitbook.io/chainlink-ccip/getting-started/how-to-use-chainlink-ccip#develop-ccip-sender-contract) subchapters.

Create a new file inside the `contracts` folder and name it `CCIPTokenSender.sol`

Start with the development by setting the Solidity compiler version and importing necessary contracts from the `@chainlink/contracts-ccip` NPM package.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender {}
```

Now let's add storage variables for the `Router.sol` smart contract address and the`LINK` token, which we will use for fees. Also, we imported the `OwnerIsCreator` smart contract because later, we will want to allow `onlyOwner` to withdraw tokens from this contract (both CCIP-BnM & LINK).

<pre class="language-solidity"><code class="lang-solidity">// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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";
<strong>import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
</strong>
contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
}
</code></pre>

Let's develop a function for sending tokens

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Before calling the Router's `ccipSend` function, ensure that your code allows users to send CCIP messages to trusted destination chains. We will also restrict that only Owner can transfer tokens from this contract.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Finally, let's develop a function to withdraw tokens from this contract.

{% code lineNumbers="true" %}

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    error NothingToWithdraw();
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
    
    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);
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Foundry" %}
You should already have [`@chianlink/contracts`](https://www.npmjs.com/package/@chainlink/contracts) and [`@chainlink/contracts-ccip`](https://www.npmjs.com/package/@chainlink/contracts-ccip) NPM packages installed. If not, please install them by referring to [The @chainlink/contracts-ccip NPM package](https://andrej-rakic.gitbook.io/chainlink-ccip/getting-started/how-to-use-chainlink-ccip#the-chainlink-contracts-ccip-npm-package) and [Develop CCIP Sender contract](https://andrej-rakic.gitbook.io/chainlink-ccip/getting-started/how-to-use-chainlink-ccip#develop-ccip-sender-contract) subchapters.

Create a new file inside the `src` folder and name it `CCIPTokenSender.sol`

Start with the development by setting the Solidity compiler version and importing necessary contracts from the `@chainlink/contracts-ccip` NPM package.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender {}
```

Now let's add storage variables for the `Router.sol` smart contract address and the`LINK` token, which we will use for fees. Also, we imported the `OwnerIsCreator` smart contract because later, we will want to allow `onlyOwner` to withdraw tokens from this contract (both CCIP-BnM & LINK).

<pre class="language-solidity"><code class="lang-solidity">// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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";
<strong>import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
</strong>
contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
}
</code></pre>

Let's develop a function for sending tokens

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Before calling the Router's `ccipSend` function, ensure that your code allows users to send CCIP messages to trusted destination chains. We will also restrict that only Owner can transfer tokens from this contract.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Finally, let's develop a function to withdraw tokens from this contract.

{% code lineNumbers="true" %}

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    error NothingToWithdraw();
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
    
    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);
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Remix" %}
Create a new Solidity file by clicking on the "Create new file" button and name it `CCIPTokenSender.sol`

Start with the development by setting the Solidity compiler version and importing necessary contracts from the `@chainlink/contracts-ccip` NPM package.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender {}
```

Now let's add storage variables for the `Router.sol` smart contract address and the`LINK` token, which we will use for fees. Also, we imported the `OwnerIsCreator` smart contract because later, we will want to allow `onlyOwner` to withdraw tokens from this contract (both CCIP-BnM & LINK).

<pre class="language-solidity"><code class="lang-solidity">// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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";
<strong>import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
</strong>
contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
}
</code></pre>

Let's develop a function for sending tokens

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Before calling the Router's `ccipSend` function, ensure that your code allows users to send CCIP messages to trusted destination chains. We will also restrict that only Owner can transfer tokens from this contract.

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
}
```

Finally, let's develop a function to withdraw tokens from this contract.

{% code lineNumbers="true" %}

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

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.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 {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

contract CCIPTokenSender is OwnerIsCreator {
    IRouterClient router;
    LinkTokenInterface linkToken;
    
    mapping(uint64 => bool) public whitelistedChains;
    
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); 
    error DestinationChainNotWhitelisted(uint64 destinationChainSelector);
    error NothingToWithdraw();
    
    event TokensTransferred(
        bytes32 indexed messageId, // The unique ID of the message.
        uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
        address receiver, // The address of the receiver on the destination chain.
        address token, // The token address that was transferred.
        uint256 tokenAmount, // The token amount that was transferred.
        address feeToken, // the token address used to pay CCIP fees.
        uint256 fees // The fees paid for sending the message.
    );
    
    modifier onlyWhitelistedChain(uint64 _destinationChainSelector) {
        if (!whitelistedChains[_destinationChainSelector])
            revert DestinationChainNotWhitelisted(_destinationChainSelector);
        _;
    }

    constructor(address _router, address _link) {
        router = IRouterClient(_router);
        linkToken = LinkTokenInterface(_link);
    }
   
    function whitelistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = true;
    }

    function denylistChain(
        uint64 _destinationChainSelector
    ) external onlyOwner {
        whitelistedChains[_destinationChainSelector] = false;
    }
    
    function transferTokens(
        uint64 _destinationChainSelector,
        address _receiver,
        address _token,
        uint256 _amount
    ) 
        external
        onlyOwner
        onlyWhitelistedChain(_destinationChainSelector)
        returns (bytes32 messageId) 
    {
        Client.EVMTokenAmount[]
            memory tokenAmounts = new Client.EVMTokenAmount[](1);
        Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
            token: _token,
            amount: _amount
        });
        tokenAmounts[0] = tokenAmount;
        
        // Build the CCIP Message
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(_receiver),
            data: "",
            tokenAmounts: tokenAmounts,
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 0, strict: false})
            ),
            feeToken: address(linkToken)
        });
        
        // CCIP Fees Management
        uint256 fees = router.getFee(_destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        linkToken.approve(address(router), fees);
        
        // Approve Router to spend CCIP-BnM tokens we send
        IERC20(_token).approve(address(router), _amount);
        
        // Send CCIP Message
        messageId = router.ccipSend(_destinationChainSelector, message); 
        
        emit TokensTransferred(
            messageId,
            _destinationChainSelector,
            _receiver,
            _token,
            _amount,
            address(linkToken),
            fees
        );   
    }
    
    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);
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Deploy CCIPTokenSender.sol

Before we begin with this section, make sure your environment variables are set. Check the [Prepare for Deployment subchapter](https://andrej-rakic.gitbook.io/chainlink-ccip/getting-started/how-to-use-chainlink-ccip#prepare-for-deployment) for reference.

Using your development environment of choice, deploy the `CCIPTokenSender` smart contract we just developed to the Avalanche Fuji testnet.

{% tabs %}
{% tab title="Hardhat" %}
Create a new file under the `scripts` folder and name it `deployTokenSender.ts` or `deployTokenSender.js` depends on whether you work with TypeScript or JavaScript Hardhat projects.

```typescript
// scripts/deployTokenSender.ts

import { ethers, network, run } from "hardhat";

async function main() {
  if(network.name !== `avalancheFuji`) {
    console.error(`❌ Sender must be deployed to Avalanche Fuji`);
    return 1;
  }

  const fujiLinkAddress = `0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846`;
  const fujiRouterAddress = `0x554472a2720E5E7D5D3C817529aBA05EEd5F82D8`;
  
  await run("compile");

  const ccipTokenSenderFactory = await ethers.getContractFactory("CCIPTokenSender");
  const ccipTokenSender = await ccipTokenSenderFactory.deploy(fujiLinkAddress, fujiRouterAddress);

  await ccipTokenSender.deployed();

  console.log(`CCIPTokenSender deployed to ${ccipTokenSender.address}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

Deploy CCIPTokenSender smart contract by running:

```sh
npx hardhat run ./scripts/deployTokenSender.ts --network avalancheFuji
```

or for JavaScript:

```sh
npx hardhat run ./scripts/deployTokenSender.js --network avalancheFuji
```

{% endtab %}

{% tab title="Foundry" %}

### Option 1)

Deploy CCIPTokenSender smart contract by running:

```sh
forge create --rpc-url avalancheFuji --private-key=$PRIVATE_KEY src/CCIPTokenSender.sol:CCIPTokenSender --constructor-args 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 0x554472a2720E5E7D5D3C817529aBA05EEd5F82D8
```

### Option 2)

Create a new smart contract under the `script` folder and name it `CCIPTokenSender.s.sol`&#x20;

```solidity
// script/CCIPTokenSender.s.sol

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

import "forge-std/Script.sol";
import {CCIPTokenSender} from "../src/CCIPTokenSender.sol";

contract DeployCCIPTokenSender is Script {
    function run() public {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        address fujiLink = 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846;
        address fujiRouter = 0x554472a2720E5E7D5D3C817529aBA05EEd5F82D8;

        CCIPTokenSender sender = new CCIPTokenSender(
            fujiLink,
            fujiRouter
        );

        console.log(
            "CCIPTokenSender deployed to ",
            address(sender)
        );

        vm.stopBroadcast();
    }
}
```

Deploy CCIPTokenSender smart contract by running:

```sh
forge script ./script/CCIPTokenSender.s.sol:DeployCCIPTokenSender -vvv --broadcast --rpc-url avalancheFuji
```

{% endtab %}

{% tab title="Remix" %}
Open your Metamask wallet and switch to the Avalanche Fuji network.&#x20;

Open the `CCIPTokenSender.sol` file.&#x20;

Navigate to the "Solidity Compiler" tab and click the "Compile CCIPTokenSender.sol" button.&#x20;

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).&#x20;

Under the "Contract" dropdown menu, make sure that the "CCIPTokenSender - CCIPTokenSender.sol" is selected. Locate the orange "Deploy" button. Provide `0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846` as the link address and `0x554472a2720E5E7D5D3C817529aBA05EEd5F82D8` as the router address. Click the orange "Deploy"/"Transact" button.&#x20;

Metamask notification will pop up. Sign the transaction.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FeocyO4JPXhq2Dx9WT9lF%2Fexercise%201%20-%20deploy.png?alt=media&#x26;token=c9da4a82-e1f5-4a17-a944-001fc9ab44fb" alt=""><figcaption><p>Deploy CCIPTokenSender.sol</p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Fund CCIPTokenSender.sol smart contract

Let's now fund our Token Sender smart contract with both CCIP-BnM and LINK tokens. We are transferring CCIP-BnM tokens to Ethereum Sepolia, and we will use LINK tokens for fees.

Using your wallet of choice, send to previously deployed `CCIPTokenSender` smart contract:

* 1 LINK
* 0.1 CCIP-BnM

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FMKMlEwRneFo2FY8F6sR6%2Ffund.png?alt=media&#x26;token=a05033af-8404-4edb-81bd-aeddfbbdfeef" alt="" width="351"><figcaption><p>Send 1 LINK</p></figcaption></figure>

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FP0uyqkQDsCDk5cmIqGH0%2Ffund%20ccipBnM.png?alt=media&#x26;token=9f565f35-f8d9-4c9b-912c-a9996656cda5" alt="" width="353"><figcaption><p>Send 0.1 CCIP-BnM</p></figcaption></figure>

## Transfer CCIP-BnM tokens

Let's transfer 100 sub-units (0.0000000000000001 tokens) of CCIP-BnM tokens to some EOA using Chainlink CCIP.

{% tabs %}
{% tab title="Hardhat" %}
Prepare:

* The address of the EOA to send tokens to, as the `_receiver` parameter;
* 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4, which is the CCIP-BnM token address on the Avalanche Fuji network, as the `_token` parameter;
* 100 as the `_amount` parameter;
* 16015286601757825753, which is the CCIP Chain Selector for the Ethereum Sepolia network, as the `_destinationChainSelector` parameter.

Create a new JavaScript/TypeScript file under the `scripts` folder and name it `transferTokens.js`/`transferTokens.ts`

```typescript
// scripts/transferTokens.ts

import { ethers, network } from "hardhat";

async function main() {
  if(network.name !== `avalancheFuji`) {
    console.error(`❌ Must be called from Avalanche Fuji`);
    return 1;
  }

  const receiver = `PUT YOUR EOA ADDRESS HERE`;
  const ccipBnMAddress = `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`;
  const amount = 100n;
  const destinationChainSelector = 16015286601757825753;

  const ccipTokenSenderFactory = await ethers.getContractFactory("CCIPTokenSender");
  const ccipTokenSender = await ccipTokenSenderFactory.connect(ccipSenderAddress, ethers.provider);
  
  const whitelistTx = await ccipTokenSender.whitelistChain(
      destinationChainSelector
  );
  
  console.log(`Whitelisted Sepolia, transaction hash: ${whitelistTx.hash}`);

  const transferTx = await ccipTokenSender.transferTokens(
      destinationChainSelector, 
      receiver,
      ccipBnMAddress,
      amount
  );

  console.log(`Tokens sent, transaction hash: ${transferTx.hash}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

Transfer CCIP-BnM by running the following command:

```sh
npx hardhat run ./scripts/transferTokens.ts --network avalancheFuji
```

Or for JavaScript:

```sh
npx hardhat run ./scripts/transferTokens.js --network avalancheFuji
```

{% endtab %}

{% tab title="Foundry" %}
Prepare:

* The address of the EOA to send tokens to, as the `_receiver` parameter;
* 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4, which is the CCIP-BnM token address on the Avalanche Fuji network, as the `_token` parameter;
* 100 as the `_amount` parameter;
* 16015286601757825753, which is the CCIP Chain Selector for the Ethereum Sepolia network, as the `_destinationChainSelector` parameter.

First of all, we need to whitelist the destination chain:

```sh
cast send <CCIP_TOKEN_SENDER_ADDRESS> --rpc-url avalancheFuji --private-key=$PRIVATE_KEY "whitelistChain(uint64)" 16015286601757825753
```

Then run:

```sh
cast send <CCIP_TOKEN_SENDER_ADDRESS> --rpc-url avalancheFuji --private-key=$PRIVATE_KEY "transferTokens(uint64,address,address,uint256)" 16015286601757825753 <RECEIVER_ADDRESS> 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 100 
```

{% endtab %}

{% tab title="Remix" %}
Under the "Deployed Contracts" section, you should find the `CCIPTokenSender.sol` smart contract you previously deployed to Avalanche Fuji.&#x20;

First, find the `whitelistChain` function and provide:

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

Hit the "Transact" orange button.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FntjhW6evJ4rudFjFm8zM%2Fexercise%201%20-%20whitelistChain.png?alt=media&#x26;token=20188ecc-bfc8-40eb-ab9c-faecd1b1b001" alt=""><figcaption><p>whitelistChain()</p></figcaption></figure>

Then find the `transferTokens` function and provide:

* The address of the EOA to send tokens to, as the `_receiver` parameter;
* 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4, which is the CCIP-BnM token address on the Avalanche Fuji network, as the `_token` parameter;
* 100 as the `_amount` parameter;
* 16015286601757825753, which is the CCIP Chain Selector for the Ethereum Sepolia network, as the `_destinationChainSelector` parameter.

Hit the "Transact" orange button.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2FMwDy0RO9yzE8KHHuS5g8%2Fexercise%201%20-%20transferTokens.png?alt=media&#x26;token=f74085b4-70b8-4e1a-b8d2-2f2db11b7a4a" alt=""><figcaption><p>transferTokens()</p></figcaption></figure>
{% endtab %}
{% endtabs %}

You can now monitor live the status of your CCIP Cross-Chain Message via [CCIP Explorer](https://ccip.chain.link/). Just paste the transaction hash into the search bar and open the message details.

<figure><img src="https://2639739539-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEm7Dwh3rUCIyHWLvZwRo%2Fuploads%2Fdyd38qXTzvb9JveR0QVy%2Fexplorer.png?alt=media&#x26;token=b43d338b-3dbf-4e72-b944-30224a3d88b4" alt="" width="563"><figcaption><p>CCIP Explorer</p></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://andrej-rakic.gitbook.io/chainlink-ccip/ccip-masterclass/exercise-1-transfer-tokens.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
