Transfer Tokens between EOAs

In this tutorial, you will use Chainlink CCIP to transfer tokens directly from your EOA (Externally Owned Account) to an account on a different blockchain. First, you will pay for CCIP fees on the source blockchain using LINK. Then, you will run the same example paying for CCIP fees in native gas, such as ETH on Ethereum or AVAX on Avalanche.

Before you begin

  1. Install Node.js 18. Optionally, you can use the nvm package to switch between Node.js versions with nvm use 18.

    node -v
    
    $ node -v
    v18.7.0
    
  2. Your EOA (Externally Owned Account) must have both AVAX and LINK tokens on Avalanche Fuji to pay for the gas fees and CCIP fees.

  3. Check the CCIP Directory to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from Avalanche Fuji to Ethereum Sepolia so check the list of supported tokens here.

  4. Learn how to acquire CCIP test tokens. After following this guide, your EOA (Externally Owned Account) should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.

  5. In a terminal, clone the smart-contract-examples repository and change to the smart-contract-examples/ccip/offchain/javascript directory.

    git clone https://github.com/smartcontractkit/smart-contract-examples.git && \
    cd smart-contract-examples/ccip/offchain/javascript
    
  6. Run npm install to install the dependencies.

    npm install
    
  7. For higher security, the examples repository imports @chainlink/env-enc. Use this tool to encrypt your environment variables at rest.

    1. Set an encryption password for your environment variables.

      npx env-enc set-pw
      
    2. Run npx env-enc set to configure a .env.enc file with the basic variables that you need to send your requests to Ethereum Sepolia.

      • AVALANCHE_FUJI_RPC_URL: Set a URL for the Avalanche Fuji testnet. You can sign up for a personal endpoint from Alchemy, Infura, or another node provider service.

      • ETHEREUM_SEPOLIA_RPC_URL: Set a URL for the Ethereum Sepolia testnet. You can sign up for a personal endpoint from Alchemy, Infura, or another node provider service.

      • PRIVATE_KEY: Find the private key for your testnet wallet. If you use MetaMask, follow the instructions to Export a Private Key.Note: The offchain script uses your private key to sign any transactions you make such as transferring tokens.

      npx env-enc set
      

Tutorial

In this example, you will transfer CCIP-BnM tokens from your EOA on Avalanche Fuji to an account on Ethereum Sepolia. The destination account could be an EOA (Externally Owned Account) or a smart contract. The example shows how to transfer CCIP-BnM tokens, but you can re-use the same example to transfer other tokens as long as they are supported for your lane.

For this example, CCIP fees are paid in LINK tokens. To learn how to pay CCIP fees in native AVAX, read the Pay in native section. To see a detailed description of the example code, read the code explanation section.

To transfer tokens and pay in LINK, use the following command:

node src/transfer-tokens.js sourceChain destinationChain destinationAccount tokenAddress amount feeTokenAddress

The feeTokenAddress parameter specifies the token address for paying CCIP fees. The supported tokens for paying fees include LINK, the native gas token of the source blockchain (ETH for Ethereum), and the wrapped native gas token (WETH for Ethereum).

Complete the following steps in your terminal:

  1. Send 1,000,000,000,000,000 (0.001 CCIP-BnM ) from your EOA on Avalanche Fuji to another account on Ethereum Sepolia:

    node src/transfer-tokens.js avalancheFuji ethereumSepolia YOUR_ACCOUNT 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
    

    Command arguments:

    ArgumentExplanation
    node src/transfer-tokens.jsNode.js will execute the JavaScript code inside the transfer-tokens.js file.
    avalancheFujiThis specifies the source blockchain, in this case, Avalanche Fuji.
    ethereumSepoliaThis specifies the destination blockchain, which is Ethereum Sepolia in this case.
    YOUR_ACCOUNTThis is the account address on the destination blockchain. You can replace this with your account address.
    0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4This is the CCIP-BnM token contract address on Avalanche Fuji. The contract addresses for each network can be found on the CCIP Directory.
    1000000000000000This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred.
    0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846Since you will pay for CCIP fees in LINK, this is the LINK token contract address on Avalanche Fuji. The LINK contract address can be found on the Link Token contracts page.
  2. Once you execute the command, you should see the following logs:

    $ node src/transfer-tokens.js avalancheFuji ethereumSepolia 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A  0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
    Estimated fees (LINK): 0.020020889739492
    
    Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4
    
    Approval done. Transaction: 0x103c20b95183380aa7c04edd0cc8b5cd6137f0b36eda931bdd23e66fd0d21251
    
    Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend fees 20020889739492000 of feeToken 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
    
    Approval done. Transaction: 0x1b6737bbf12f1ba0391ae9ba38c46c72a1118f4d20767c4c67729cf3acc0ae8b
    
    Calling the router to send 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP
    
    
    ✅ 1000000000000000 of Tokens(0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4) Sent to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP. Transaction hash 0x96b5b645f4f442131fc9466ff459e2211d408058be4d9a72a8fb057ca0f4723f -  Message id is 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
    
    Wait for message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd to be executed on the destination chain - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
    ...
    
  3. Analyze the logs:

    • The script communicates with the router to calculate the transaction fees required to transfer tokens, which amounts to 20,020,889,739,492,000 Juels (equivalent to 0.02 LINK).
    • The script engages with the Link token contract, authorizing the router contract to spend 20,020,889,739,492,000 Juels for the fees and 1000000000000000 (0.001 CCIP-BnM) from your Externally Owned Account (EOA) balance.
    • The script initiates a transaction through the router to transfer 1000000000000000 (0.001 CCIP-BnM) to your account on Ethereum Sepolia. It also returns the CCIP message ID.
    • The script continuously monitors the destination blockchain (Ethereum Sepolia) to track the progress and completion of the cross-chain transaction.
  4. While the script is waiting for the cross-chain transaction to proceed, open the CCIP explorer and search your cross-chain transaction using the message ID. Notice that the status is not finalized yet.

  5. After several minutes (the waiting time depends on the finality of the source blockchain), the script will complete the polling process, and the following logs will be displayed:

    Message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd has not been processed yet on the destination chain.Try again in 60sec - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
    
    ✅Status of message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd is SUCCESS - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
    
  6. Open the CCIP explorer and use the message ID to find your cross-chain transaction.

    Chainlink CCIP Explorer transaction details
  7. The data field is empty because only tokens are transferred. The gas limit is set to 0 because the transaction is directed to an Externally Owned Account (EOA). With an empty data field, no function calls on a smart contract are expected on the destination chain.

Transfer tokens and pay in native

In this example, you will transfer CCIP-BnM tokens from your EOA on Avalanche Fuji to an account on Ethereum Sepolia. The destination account could be an EOA (Externally Owned Account) or a smart contract. The example shows how to transfer LINK tokens, but you can re-use the same example to transfer other tokens as long as they are supported for your lane.

For this example, CCIP fees are paid in Sepolia's native ETH. To learn how to pay CCIP fees in LINK, read the Pay in LINK section. To see a detailed description of the example code, read the code explanation section.

To transfer tokens and pay in native, use the following command:

node src/transfer-tokens.js sourceChain destinationChain destinationAccount tokenAddress amount

Complete the following steps in your terminal:

  1. Send 1,000,000,000,000,000 (0.001 CCIP-BnM ) from your EOA on Avalanche Fuji to another account on Ethereum Sepolia:

    node src/transfer-tokens.js avalancheFuji ethereumSepolia YOUR_ACCOUNT 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000
    

    Command arguments:

    ArgumentExplanation
    node src/transfer-tokens.jsNode.js will execute the JavaScript code inside the transfer-tokens.js file.
    avalancheFujiThis specifies the source blockchain, in this case, Avalanche Fuji.
    ethereumSepoliaThis specifies the destination blockchain, which is Ethereum Sepolia in this case.
    YOUR_ACCOUNTThis is the account address on the destination blockchain. Replace this with your account address.
    0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4This is the CCIP-BnM token contract address on Avalanche Fuji. The contract addresses for each network can be found on the CCIP Directory.
    1000000000000000This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred.
  2. After you execute the command, you should see the following logs:

    $ node src/transfer-tokens.js avalancheFuji ethereumSepolia 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A  0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000
    Estimated fees (native): 80139264929946666
    
    Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4
    
    Approval done. Transaction: 0x23fd23e7df77ef6619ed108f507e85108e5e8592bc754a85b1264f8cf15e3221
    
    Calling the router to send 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP
    
    
    ✅ 1000000000000000 of Tokens(0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4) Sent to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP. Transaction hash 0xe4b2226a55a6eb27f5e5ecf497af932578bbdc1009f412a8b7a855a5dbd00ffa -  Message id is 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
    
    Wait for message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d to be executed on the destination chain - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
    ...
    
  3. Analyze the logs:

    • The script communicates with the router to calculate the transaction fees required to transfer tokens, which amounts to 80,139,264,929,946,666 wei (equivalent to 0.08 AVAX).
    • The script interacts with the CCIP-BnM token contract, authorizing the router contract to deduct 0.001 CCIP-BnM from your Externally Owned Account (EOA) balance.
    • The script initiates a transaction through the router to transfer 0.001 CCIP-BnM tokens to your destination account on Ethereum Sepolia. It also returns the CCIP message ID.
    • The script continuously monitors the destination blockchain (Ethereum Sepolia) to track the progress and completion of the cross-chain transaction.
  4. The transaction time depends on the finality of the source blockchain. After several minutes, the script will complete the polling process and the following logs will be displayed:

    Message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d is not processed yet on destination chain.Try again in 60sec - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d

    ✅Status of message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d is SUCCESS - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
  1. Open the CCIP explorer and use the message ID to find your cross-chain transaction.

    Chainlink CCIP Explorer transaction details
  2. The data field is empty because only tokens are transferred. The gas limit is set to 0 because the transaction is directed to an Externally Owned Account (EOA), so no function calls are expected on the destination chain.

Code explanation

The Javascript featured in this tutorial is designed to interact with CCIP to transfer tokens. The contract code includes several code comments to clarify each step, but this section explains the key elements.

Imports

The script starts by importing the necessary modules and data. It imports ethers.js and ABIs (Application Binary Interface) from a config file for different contracts and configurations.

Handling arguments

The handleArguments function validates and parses the command line arguments passed to the script.

Main function: transferTokens

This asynchronous function, transferTokens performs the token transfer.

Initialization

The script initializes ethers providers to communicate with the blockchains in this section. It parses source and destination router addresses and blockchain selectors. A signer is created to sign transactions.

Token validity check

The script fetches a list of supported tokens for the destination chain and checks if the token you want to transfer is supported.

Building the CCIP message

A Cross-Chain Interoperability Protocol (CCIP) message is built, which will be sent to the router contract. It includes the destination account, amount, token address, and additional parameters.

Fee calculation

The script calls the router to estimate the fees for transferring tokens.

Transferring tokens

This section handles the actual transferring of tokens. It covers three cases:

  • Fees paid using the native gas token: The contract makes one approval for the transfer amount. The fees are included in the value transaction field.
  • Fees paid using an asset different from the native gas token and the token being transferred: The contracts makes two approvals. The first approval is for the transfer amount and the second approval is for the fees.
  • Fees paid using the same asset that is being transferred, but not the native gas token: The contract makes a single approval for the sum of the transfer amount and fees.

The script waits for the transaction to be validated and stores the transaction receipt.

Fetching message ID

The router's ccipSend function returns a message ID. The script simulates a call to the blockchain to fetch the message ID that the router returned.

Checking the status on the destination chain

The script polls the off-ramp contracts on the destination chain to wait for the message to be executed. If the message is executed, it returns the status. Otherwise, the message times out after 40 minutes.

What's next

Get the latest Chainlink content straight to your inbox.