A tutorial that teaches how to use Chainlink CCIP to perform cross-chain messaging and token transfers from Base Goerli testnet to Optimism Goerli testnet.
curl -L https://foundry.paradigm.xyz | bash
foundryup
, to install the latest (nightly) build of FoundryCapability | Description | Supported receivers |
---|---|---|
Arbitrary messaging | Send arbitrary (encoded) data from one chain to another. | Smart contracts only |
Token transfers | Send tokens from one chain to another. | Smart contracts or EOAs |
Programmable token transfers | Send tokens and arbitrary (encoded) data from one chain to another, in a single transaction. | Smart contracts only |
lock
or burn
tokens if a token transfer is being made.
Lock and Unlock
or Burn and Mint
mechanism, depending on the type of token.
For example, because blockchain-native gas tokens (i.e. ETH, MATIC, AVAX) can only be minted on their native chains, a Lock and Mint
mechanism must be used. This mechanism locks the token at the source chain, and mints a synthetic asset on the destination chain.
In contrast, tokens that can be minted on multiple chains (i.e. USDC, USDT, FRAX, etc.), token pools can use a Burn and Mint
mechanism, where the token is burnt on the source chain and minted on the destination chain.
src/Counter.sol
, test/Counter.t.sol
, and script/Counter.s.sol
boilerplate files that were generated with the project, as you will not be needing them.forge install
.
To install Chainlink CCIP smart contracts, run:
foundry.toml
file by appending the following line:
Sender
contract: A smart contract that interacts with CCIP to send data and tokens.Receiver
contract: A smart contract that interacts with CCIP to receive data and tokens.src/
directory named Sender.sol
and copy the code above into the file.
Sender
contract provided above.
Sender
contract will need access to the following dependencies:
Router
contract: This contract serves as the primary interface when using CCIP to send and receive messages and tokens.Router
contract address and LINK token address are passed in as parameters to the contract’s constructor and stored as member variables for later for sending messages and paying any associated fees.
Router
contract provides two important methods that can be used when sending messages using CCIP:
getFee
: Given a chain selector and message, returns the fee amount required to send the message.ccipSend
: Given a chain selector and message, sends the message through the router and returns an associated message ID.Sender
contract defines a custom method named sendMessage
that utilizes the methods described above in order to:
EVM2AnyMessage
method provided by the Client
CCIP library, using the following data:
receiver
: The receiver contract address (encoded).data
: The text data to send with the message (encoded).tokenAmounts
: The amount of tokens to send with the message. For sending just an arbitrary message this field is defined as an empty array (new Client.EVMTokenAmount[](0)
), indicating that no tokens will be sent.extraArgs
: Extra arguments associated with the message, such as gasLimit
.feeToken
: The address
of the token to be used for paying fees.getFee
method provided by the Router
contract.Router
contract to transfer tokens on the Sender
contracts behalf in order to cover the fees.Router
contract’s ccipSend
method.src/
directory named Receiver.sol
and copy the code above into the file.
Receiver
contract provided above.
Receiver
contract will need to extend to theCCIPReceiver
interface. Extending this interface allows the Receiver
contract to initialize the contract with the router address from the constructor, as seen below:
CCIPReceiver
interface also allows the Receiver
contract to override the _ccipReceive
handler method for when a message is received and define custom logic.
Receiver
contract in this tutorial provides custom logic that stores the messageId
and text
(decoded) as member variables.
Receiver
contract defines a custom method named getMessage
that returns the details of the last received message _messageId
and _text
. This method can be called to fetch the message data details after the _ccipReceive
receives a new message.
cast wallet import
command to import the private key of the wallet into Foundry’s securely encrypted keystore:
deployer
account in your Foundry project, run:
.env
file in the home directory of your project, and add the RPC URLs, CCIP chain selectors, CCIP router addresses, and LINK token addresses for both Base Goerli and Optimism Goerli testnets:
.env
file has been created, run the following command to load the environment variables in the current command line session:
forge create
command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
Sender
smart contract to the Base Goerli testnet, run the following command:
Receiver
smart contract to the Optimism Goerli testnet, run the following command:
Sender
contract will need to hold a balance of LINK tokens.
Fund your contract directly from your wallet, or by running the following cast
command:
5
LINK tokens on Base Goerli testnet to the Sender
contract.
SENDER_CONTRACT_ADDRESS
with the contract address of your deployed Sender contract before running the provided cast command.cast
command-line tool that can be used to interact with deployed smart contracts and call their functions.
cast
command can be used to call the sendMessage(uint64, address, string)
function on the Sender
contract deployed to Base Goerli in order to send message data to the Receiver
contract on Optimism Goerli.
To call the sendMessage(uint64, address, string)
function of the Sender
smart contract, run:
sendMessage(uint64, address, string)
to send a message. The parameters passed in to the method include: The chain selector to the destination chain (Optimism Goerli), the Receiver
contract address, and the text data to be included in the message (Based
).
SENDER_CONTRACT_ADDRESS
and RECEIVER_CONTRACT_ADDRESS
with the contract addresses of your deployed Sender and Receiver contracts respectively before running the provided cast command.messageId
should be returned.
Once the transaction has been finalized, it will take a few minutes for CCIP to deliver the data to Optimism Goerli and call the ccipReceive
function on the Receiver
contract.
cast
command can also be used to call the getMessage()
function on the Receiver
contract deployed to Optimism Goerli in order to read the received message data.
To call the getMessage()
function of the Receiver
smart contract, run:
RECEIVER_CONTRACT_ADDRESS
with the contract addresses of your deployed Receiver contract before running the provided cast command.messageId
and text
of the last received message should be returned.
If the transaction fails, ensure the status of your ccipSend
transaction has been finalized. You can using the CCIP explorer.