|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity 0.8.26; |
| 3 | + |
| 4 | +import {IRouterClient} from "@chainlink/contracts/src/v0.8/ccip/interfaces/IRouterClient.sol"; |
| 5 | +import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol"; |
| 6 | +import {Client} from "@chainlink/contracts/src/v0.8/ccip/libraries/Client.sol"; |
| 7 | +import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol"; |
| 8 | + |
| 9 | +/// @title SepoliaSender |
| 10 | +/// @author Luo Yingjie |
| 11 | +/// @notice This contract will send a cross chain message from Sepolia to Amoy to trigger the token transfer |
| 12 | +contract SepoliaSender is OwnerIsCreator { |
| 13 | + // Custom errors to provide more descriptive revert messages. |
| 14 | + error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance. |
| 15 | + |
| 16 | + // Event emitted when a message is sent to another chain. |
| 17 | + event MessageSent( |
| 18 | + bytes32 indexed messageId, // The unique ID of the CCIP message. |
| 19 | + uint64 indexed destinationChainSelector, // The chain selector of the destination chain. |
| 20 | + address receiver, // The address of the receiver on the destination chain. |
| 21 | + bytes signedMessage, // The signed message that approves the token transfer |
| 22 | + address feeToken, // the token address used to pay CCIP fees. |
| 23 | + uint256 fees // The fees paid for sending the CCIP message. |
| 24 | + ); |
| 25 | + |
| 26 | + IRouterClient private s_router; |
| 27 | + |
| 28 | + LinkTokenInterface private s_linkToken; |
| 29 | + |
| 30 | + /// @notice Constructor initializes the contract with the router address. |
| 31 | + /// @param _router The address of the router contract. |
| 32 | + /// @param _link The address of the link contract. |
| 33 | + constructor(address _router, address _link) { |
| 34 | + s_router = IRouterClient(_router); |
| 35 | + s_linkToken = LinkTokenInterface(_link); |
| 36 | + } |
| 37 | + |
| 38 | + // This function should send the data which includes the amount to send, token address, and receiver address, and sign the approval of the token transfer |
| 39 | + function sendMessage( |
| 40 | + uint64 destinationChainSelector, |
| 41 | + address receiver, |
| 42 | + bytes calldata signedMessage // This is the signed message that approves the token transfer |
| 43 | + ) external onlyOwner returns (bytes32 messageId) { |
| 44 | + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message |
| 45 | + Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ |
| 46 | + receiver: abi.encode(receiver), // ABI-encoded receiver address |
| 47 | + data: abi.encode(signedMessage), |
| 48 | + tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent |
| 49 | + extraArgs: Client._argsToBytes( |
| 50 | + // Additional arguments, setting gas limit and allowing out-of-order execution. |
| 51 | + // Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach |
| 52 | + // where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages, |
| 53 | + // and ensures compatibility with future CCIP upgrades. Read more about it here: https://docs.chain.link/ccip/best-practices#using-extraargs |
| 54 | + Client.EVMExtraArgsV2({ |
| 55 | + gasLimit: 200_000, // Gas limit for the callback on the destination chain |
| 56 | + allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender |
| 57 | + }) |
| 58 | + ), |
| 59 | + // Set the feeToken address, indicating LINK will be used for fees |
| 60 | + feeToken: address(s_linkToken) |
| 61 | + }); |
| 62 | + |
| 63 | + // Get the fee required to send the message |
| 64 | + uint256 fees = s_router.getFee( |
| 65 | + destinationChainSelector, |
| 66 | + evm2AnyMessage |
| 67 | + ); |
| 68 | + |
| 69 | + if (fees > s_linkToken.balanceOf(address(this))) |
| 70 | + revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); |
| 71 | + |
| 72 | + // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK |
| 73 | + s_linkToken.approve(address(s_router), fees); |
| 74 | + |
| 75 | + // Send the message through the router and store the returned message ID |
| 76 | + messageId = s_router.ccipSend(destinationChainSelector, evm2AnyMessage); |
| 77 | + |
| 78 | + // Emit an event with message details |
| 79 | + emit MessageSent( |
| 80 | + messageId, |
| 81 | + destinationChainSelector, |
| 82 | + receiver, |
| 83 | + signedMessage, |
| 84 | + address(s_linkToken), |
| 85 | + fees |
| 86 | + ); |
| 87 | + |
| 88 | + // Return the message ID |
| 89 | + return messageId; |
| 90 | + } |
| 91 | +} |
0 commit comments