Exercise #2: Transfer Tokens & Data
Mint an NFT on one chain and pay for minting on another
Getting started
Adjust CCIPTokenSender.sol from Exercise #1
// 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 CCIPTokenAndDataSender 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: abi.encodeWithSignature("mint(address)", msg.sender),
tokenAmounts: tokenAmounts,
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({gasLimit: 200_000, 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);
}
}Develop the CCIPTokenAndDataReceiver.sol smart contract
Deployment and Usage


Last updated