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 " ;
1
14
2
15
library ChainlinkCrossChainStorage {
3
16
4
17
/// @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 ));
7
21
8
22
struct Data {
9
23
address router;
10
- address s_linkToken ;
24
+ address linkToken ;
11
25
}
12
26
13
27
function data () internal pure returns (Data storage data_ ) {
@@ -19,134 +33,159 @@ library ChainlinkCrossChainStorage {
19
33
20
34
}
21
35
36
+ contract ChainlinkCrossChain is Module {
37
+
38
+ error NotEnoughBalance (uint256 currentBalance , uint256 calculatedFees );
39
+
40
+ /*//////////////////////////////////////////////////////////////
41
+ MODULE CONFIG
42
+ //////////////////////////////////////////////////////////////*/
22
43
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 );
24
47
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 });
27
56
28
- constructor (address _router , address _link ) {
29
- s_linkToken = _link;
30
- router = _router;
57
+ config.registerInstallationCallback = true ;
31
58
}
32
59
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
+ //////////////////////////////////////////////////////////////*/
42
63
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
+ }
45
70
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 {}
48
73
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
+ }
51
78
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
+ }
55
83
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
+ //////////////////////////////////////////////////////////////*/
58
87
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
+ }
61
95
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
+ }
64
99
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
+ }
69
103
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
+ }
72
122
}
73
123
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 ,
87
132
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);
105
142
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);
108
145
}
109
146
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);
111
148
IERC20 (_token).approve (address (router), _amount);
149
+ router.ccipSend (_destinationChain, evm2AnyMessage);
150
+ }
112
151
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);
115
164
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
+ }
118
168
119
- // Return the message ID
120
- return messageId ;
169
+ IERC20 (_token). approve ( address (router), _amount);
170
+ router. ccipSend {value: fees}(_destinationChain, evm2AnyMessage) ;
121
171
}
122
172
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.
132
173
function _buildCCIPMessage (
133
- address _recipient
174
+ address _recipient ,
134
175
bytes calldata _data ,
135
176
address _token ,
136
177
uint256 _amount ,
137
178
address _feeTokenAddress ,
138
- bytes calldata _extraArgs
179
+ bytes memory _extraArgs
139
180
) private pure returns (Client.EVM2AnyMessage memory ) {
140
- // Set the token amounts
141
181
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount [](1 );
142
182
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
+
144
184
return Client.EVM2AnyMessage ({
145
- receiver: abi.encode (_recipient), // ABI-encoded receiver address
185
+ receiver: abi.encode (_recipient),
146
186
data: _data,
147
- tokenAmounts: tokenAmounts, // The amount and type of token being transferred
187
+ tokenAmounts: tokenAmounts,
148
188
extraArgs: _extraArgs,
149
- // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
150
189
feeToken: _feeTokenAddress
151
190
});
152
191
}
0 commit comments