1+ // SPDX-License-Identifier: Apache-2.0
2+ pragma solidity ^ 0.8.20 ;
3+
4+ import {Module} from "../../../Module.sol " ;
5+
6+ import {Role} from "../../../Role.sol " ;
7+
8+ import {IERC20 } from "../../../interface/IERC20.sol " ;
9+ import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol " ;
10+ import {OwnableRoles} from "@solady/auth/OwnableRoles.sol " ;
11+
12+ import {IRouterClient} from "@chainlink/ccip/interfaces/IRouterClient.sol " ;
13+ import {Client} from "@chainlink/ccip/libraries/Client.sol " ;
114
215library ChainlinkCrossChainStorage {
316
417 /// @custom:storage-location erc7201:token.minting.chainlinkcrosschain
5- bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION =
6- keccak256 (abi.encode (uint256 (keccak256 ("token.minting.chainlinkcrosschain.erc721 " )) - 1 )) & ~ bytes32 (uint256 (0xff ));
18+ bytes32 public constant CHAINLINKCROSSCHAIN_STORAGE_POSITION = keccak256 (
19+ abi.encode (uint256 (keccak256 ("token.minting.chainlinkcrosschain.erc721 " )) - 1 )
20+ ) & ~ bytes32 (uint256 (0xff ));
721
822 struct Data {
923 address router;
10- address s_linkToken ;
24+ address linkToken ;
1125 }
1226
1327 function data () internal pure returns (Data storage data_ ) {
@@ -19,134 +33,159 @@ library ChainlinkCrossChainStorage {
1933
2034}
2135
36+ contract ChainlinkCrossChain is Module {
37+
38+ error NotEnoughBalance (uint256 currentBalance , uint256 calculatedFees );
39+
40+ /*//////////////////////////////////////////////////////////////
41+ MODULE CONFIG
42+ //////////////////////////////////////////////////////////////*/
2243
23- contract ChainlinkCrossChain is CCIPReceiver , OwnerIsCreator {
44+ /// @notice Returns all implemented callback and fallback functions.
45+ function getModuleConfig () external pure override returns (ModuleConfig memory config ) {
46+ config.fallbackFunctions = new FallbackFunction [](5 );
2447
25- address immutable s_linkToken;
26- address immutable router;
48+ config.fallbackFunctions[0 ] = FallbackFunction ({selector: this .getRouter.selector , permissionBits: 0 });
49+ config.fallbackFunctions[1 ] = FallbackFunction ({selector: this .getLinkToken.selector , permissionBits: 0 });
50+ config.fallbackFunctions[2 ] =
51+ FallbackFunction ({selector: this .setRouter.selector , permissionBits: Role._MANAGER_ROLE});
52+ config.fallbackFunctions[3 ] =
53+ FallbackFunction ({selector: this .setLinkToken.selector , permissionBits: Role._MANAGER_ROLE});
54+ config.fallbackFunctions[4 ] =
55+ FallbackFunction ({selector: this .sendCrossChainTransaction.selector , permissionBits: 0 });
2756
28- constructor (address _router , address _link ) {
29- s_linkToken = _link;
30- router = _router;
57+ config.registerInstallationCallback = true ;
3158 }
3259
33- function bridgeWithToken (
34- address _destinationChain ,
35- address _recipient ,
36- bytes memory _data ,
37- address _token ,
38- uint256 _amount ,
39- bytes memory _extraArgs ,
40- ) external {
41- (uint256 _feeTokenAddress , ccipMessageExtraArgs) = abi.decode (_extraArgs, (uint256 , bytes ));
60+ /*//////////////////////////////////////////////////////////////
61+ INSTALL / UNINSTALL FUNCTIONS
62+ //////////////////////////////////////////////////////////////*/
4263
43- Client.EVM2AnyMessage memory evm2AnyMessage =
44- _buildCCIPMessage (_receiver, _text, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs);
64+ /// @dev Called by a Core into an Module during the installation of the Module.
65+ function onInstall (bytes calldata data ) external {
66+ (address router , address linkToken ) = abi.decode (data, (address , address ));
67+ _chainlinkCrossChainStorage ().router = router;
68+ _chainlinkCrossChainStorage ().linkToken = linkToken;
69+ }
4570
46- // Initialize a router client instance to interact with cross-chain router
47- IRouterClient router = IRouterClient ( _chainLinkCrossChainStorage ().router);
71+ /// @dev Called by a Core into an Module during the uninstallation of the Module.
72+ function onUninstall ( bytes calldata data ) external {}
4873
49- // Get the fee required to send the CCIP message
50- uint256 fees = router.getFee (_destinationChainSelector, evm2AnyMessage);
74+ /// @dev Returns bytes encoded install params, to be sent to `onInstall` function
75+ function encodeBytesOnInstall (address router , address linkToken ) external pure returns (bytes memory ) {
76+ return abi.encode (router, linkToken);
77+ }
5178
52- if (fees > s_linkToken.balanceOf (address (this ))) {
53- revert NotEnoughBalance (s_linkToken.balanceOf (address (this )), fees);
54- }
79+ /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
80+ function encodeBytesOnUninstall () external pure returns (bytes memory ) {
81+ return "" ;
82+ }
5583
56- // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
57- s_linkToken.approve (address (router), fees);
84+ /*//////////////////////////////////////////////////////////////
85+ FALLBACK FUNCTIONS
86+ //////////////////////////////////////////////////////////////*/
5887
59- // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
60- IERC20 (_token).approve (address (router), _amount);
88+ function getRouter () external view returns (address ) {
89+ return _chainlinkCrossChainStorage ().router;
90+ }
91+
92+ function getLinkToken () external view returns (address ) {
93+ return _chainlinkCrossChainStorage ().linkToken;
94+ }
6195
62- // Send the message through the router and store the returned message ID
63- messageId = router.ccipSend (_destinationChainSelector, evm2AnyMessage);
96+ function setRouter (address router ) external {
97+ _chainlinkCrossChainStorage ().router = router;
98+ }
6499
65- // Emit an event with message details
66- emit MessageSent (
67- messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address (s_linkToken), fees
68- );
100+ function setLinkToken (address linkToken ) external {
101+ _chainlinkCrossChainStorage ().linkToken = linkToken;
102+ }
69103
70- // Return the message ID
71- return messageId;
104+ function sendCrossChainTransaction (
105+ uint64 _destinationChain ,
106+ address _recipient ,
107+ bytes calldata _data ,
108+ address _token ,
109+ uint256 _amount ,
110+ address _callAddress ,
111+ bytes memory _extraArgs
112+ ) external {
113+ (address _feeTokenAddress , bytes memory ccipMessageExtraArgs ) = abi.decode (_extraArgs, (address , bytes ));
114+
115+ if (_feeTokenAddress == address (0 )) {
116+ _sendMessagePayNative (_destinationChain, _recipient, _data, _token, _amount, ccipMessageExtraArgs);
117+ } else {
118+ _sendMessagePayToken (
119+ _destinationChain, _recipient, _data, _token, _amount, _feeTokenAddress, ccipMessageExtraArgs
120+ );
121+ }
72122 }
73123
74- /// @notice Sends data and transfer tokens to receiver on the destination chain.
75- /// @notice Pay for fees in native gas.
76- /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
77- /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
78- /// @param _receiver The address of the recipient on the destination blockchain.
79- /// @param _text The string data to be sent.
80- /// @param _token token address.
81- /// @param _amount token amount.
82- /// @return messageId The ID of the CCIP message that was sent.
83- function sendMessagePayNative (
84- uint64 _destinationChainSelector ,
85- address _receiver ,
86- string calldata _text ,
124+ /*//////////////////////////////////////////////////////////////
125+ INTERNAL FUNCTIONS
126+ //////////////////////////////////////////////////////////////*/
127+
128+ function _sendMessagePayToken (
129+ uint64 _destinationChain ,
130+ address _recipient ,
131+ bytes calldata _data ,
87132 address _token ,
88- uint256 _amount
89- )
90- external
91- onlyOwner
92- onlyAllowlistedDestinationChain (_destinationChainSelector)
93- validateReceiver (_receiver)
94- returns (bytes32 messageId )
95- {
96- // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
97- // address(0) means fees are paid in native gas
98- Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage (_receiver, _text, _token, _amount, address (0 ));
99-
100- // Initialize a router client instance to interact with cross-chain router
101- IRouterClient router = IRouterClient (this .getRouter ());
102-
103- // Get the fee required to send the CCIP message
104- uint256 fees = router.getFee (_destinationChainSelector, evm2AnyMessage);
133+ uint256 _amount ,
134+ address _feeTokenAddress ,
135+ bytes memory _extraArgs
136+ ) internal {
137+ Client.EVM2AnyMessage memory evm2AnyMessage =
138+ _buildCCIPMessage (_recipient, _data, _token, _amount, _feeTokenAddress, _extraArgs);
139+ IRouterClient router = IRouterClient (_chainlinkCrossChainStorage ().router);
140+ uint256 fees = router.getFee (_destinationChain, evm2AnyMessage);
141+ IERC20 linkToken = IERC20 (_chainlinkCrossChainStorage ().linkToken);
105142
106- if (fees > address (this ).balance ) {
107- revert NotEnoughBalance (address (this ).balance , fees);
143+ if (fees > linkToken. balanceOf ( address (this )) ) {
144+ revert NotEnoughBalance (linkToken. balanceOf ( address (this )) , fees);
108145 }
109146
110- // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
147+ IERC20 (linkToken). approve ( address (router), fees);
111148 IERC20 (_token).approve (address (router), _amount);
149+ router.ccipSend (_destinationChain, evm2AnyMessage);
150+ }
112151
113- // Send the message through the router and store the returned message ID
114- messageId = router.ccipSend {value: fees}(_destinationChainSelector, evm2AnyMessage);
152+ function _sendMessagePayNative (
153+ uint64 _destinationChain ,
154+ address _recipient ,
155+ bytes calldata _data ,
156+ address _token ,
157+ uint256 _amount ,
158+ bytes memory _extraArgs
159+ ) internal {
160+ Client.EVM2AnyMessage memory evm2AnyMessage =
161+ _buildCCIPMessage (_recipient, _data, _token, _amount, address (0 ), _extraArgs);
162+ IRouterClient router = IRouterClient (_chainlinkCrossChainStorage ().router);
163+ uint256 fees = router.getFee (_destinationChain, evm2AnyMessage);
115164
116- // Emit an event with message details
117- emit MessageSent (messageId, _destinationChainSelector, _receiver, _text, _token, _amount, address (0 ), fees);
165+ if (fees > address (this ).balance) {
166+ revert NotEnoughBalance (address (this ).balance, fees);
167+ }
118168
119- // Return the message ID
120- return messageId ;
169+ IERC20 (_token). approve ( address (router), _amount);
170+ router. ccipSend {value: fees}(_destinationChain, evm2AnyMessage) ;
121171 }
122172
123-
124- /// @notice Construct a CCIP message.
125- /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer.
126- /// @param _receiver The address of the receiver.
127- /// @param _text The string data to be sent.
128- /// @param _token The token to be transferred.
129- /// @param _amount The amount of the token to be transferred.
130- /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
131- /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
132173 function _buildCCIPMessage (
133- address _recipient
174+ address _recipient ,
134175 bytes calldata _data ,
135176 address _token ,
136177 uint256 _amount ,
137178 address _feeTokenAddress ,
138- bytes calldata _extraArgs
179+ bytes memory _extraArgs
139180 ) private pure returns (Client.EVM2AnyMessage memory ) {
140- // Set the token amounts
141181 Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount [](1 );
142182 tokenAmounts[0 ] = Client.EVMTokenAmount ({token: _token, amount: _amount});
143- // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
183+
144184 return Client.EVM2AnyMessage ({
145- receiver: abi.encode (_recipient), // ABI-encoded receiver address
185+ receiver: abi.encode (_recipient),
146186 data: _data,
147- tokenAmounts: tokenAmounts, // The amount and type of token being transferred
187+ tokenAmounts: tokenAmounts,
148188 extraArgs: _extraArgs,
149- // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
150189 feeToken: _feeTokenAddress
151190 });
152191 }
0 commit comments